use crate::cast::As;
use std::cmp::{Ordering, PartialOrd};
use std::fmt::{Display, Error, Formatter};
use std::ops::Not;
use std::str::FromStr;
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum QrError {
DataTooLong,
InvalidVersion { version: Version, ec_level: EcLevel },
UnsupportedCharacterSet,
InvalidEciDesignator { value: u32 },
InvalidCharacter { position: usize, byte: u8 },
}
impl Display for QrError {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
match self {
QrError::DataTooLong => fmt.write_str("data too long to encode"),
QrError::InvalidVersion { version, ec_level } => {
write!(fmt, "invalid version {version:?} for error correction level {ec_level:?}")
}
QrError::UnsupportedCharacterSet => fmt.write_str("unsupported character set for this version"),
QrError::InvalidEciDesignator { value } => {
write!(fmt, "invalid ECI designator {value} (must be 0..=999999)")
}
QrError::InvalidCharacter { position, byte } => {
write!(fmt, "invalid character byte 0x{byte:02x} at position {position}")
}
}
}
}
impl ::std::error::Error for QrError {}
pub type QrResult<T> = Result<T, QrError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnumParseError(pub &'static str);
impl Display for EnumParseError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
f.write_str(self.0)
}
}
impl ::std::error::Error for EnumParseError {}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Color {
Light,
Dark,
}
impl Color {
pub fn select<T>(self, dark: T, light: T) -> T {
match self {
Color::Light => light,
Color::Dark => dark,
}
}
}
impl Not for Color {
type Output = Self;
fn not(self) -> Self {
match self {
Color::Light => Color::Dark,
Color::Dark => Color::Light,
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
pub enum EcLevel {
L = 0,
M = 1,
Q = 2,
H = 3,
}
impl FromStr for EcLevel {
type Err = EnumParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim() {
"l" | "L" => Ok(EcLevel::L),
"m" | "M" => Ok(EcLevel::M),
"q" | "Q" => Ok(EcLevel::Q),
"h" | "H" => Ok(EcLevel::H),
_ => Err(EnumParseError("expected one of L, M, Q, H")),
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Version {
Normal(i16),
Micro(i16),
}
impl Version {
pub const fn width(self) -> i16 {
match self {
Version::Normal(v) => v * 4 + 17,
Version::Micro(v) => v * 2 + 9,
}
}
pub fn fetch<T>(self, ec_level: EcLevel, table: &[[T; 4]]) -> QrResult<T>
where
T: PartialEq + Default + Copy,
{
match self {
Version::Normal(v @ 1..=40) => {
return Ok(table[(v - 1).as_usize()][ec_level as usize]);
}
Version::Micro(v @ 1..=4) => {
let obj = table[(v + 39).as_usize()][ec_level as usize];
if obj != T::default() {
return Ok(obj);
}
}
_ => {}
}
Err(QrError::InvalidVersion { version: self, ec_level })
}
pub fn mode_bits_count(self) -> usize {
if let Version::Micro(a) = self { (a - 1).as_usize() } else { 4 }
}
pub fn is_micro(self) -> bool {
matches!(self, Version::Micro(_))
}
}
impl FromStr for Version {
type Err = EnumParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let (micro, digits) = match s.chars().next() {
Some('M' | 'm') => (true, &s[1..]),
_ => (false, s),
};
let n: i16 = digits.parse().map_err(|_| EnumParseError("expected a version number"))?;
if micro {
if (1..=4).contains(&n) {
Ok(Version::Micro(n))
} else {
Err(EnumParseError("Micro QR version must be between M1 and M4"))
}
} else if (1..=40).contains(&n) {
Ok(Version::Normal(n))
} else {
Err(EnumParseError("QR version must be between 1 and 40"))
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Mode {
Numeric,
Alphanumeric,
Byte,
Kanji,
}
impl Mode {
pub fn length_bits_count(self, version: Version) -> usize {
match version {
Version::Micro(a) => {
let a = a.as_usize();
match self {
Mode::Numeric => 2 + a,
Mode::Alphanumeric | Mode::Byte => 1 + a,
Mode::Kanji => a,
}
}
Version::Normal(1..=9) => match self {
Mode::Numeric => 10,
Mode::Alphanumeric => 9,
Mode::Byte | Mode::Kanji => 8,
},
Version::Normal(10..=26) => match self {
Mode::Numeric => 12,
Mode::Alphanumeric => 11,
Mode::Byte => 16,
Mode::Kanji => 10,
},
Version::Normal(_) => match self {
Mode::Numeric => 14,
Mode::Alphanumeric => 13,
Mode::Byte => 16,
Mode::Kanji => 12,
},
}
}
pub fn data_bits_count(self, raw_data_len: usize) -> usize {
match self {
Mode::Numeric => (raw_data_len * 10).div_ceil(3),
Mode::Alphanumeric => (raw_data_len * 11).div_ceil(2),
Mode::Byte => raw_data_len * 8,
Mode::Kanji => raw_data_len * 13,
}
}
#[must_use]
pub fn max(self, other: Self) -> Self {
match self.partial_cmp(&other) {
Some(Ordering::Greater) => self,
Some(_) => other,
None => Mode::Byte,
}
}
}
impl PartialOrd for Mode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(a, b) if a == b => Some(Ordering::Equal),
(Mode::Numeric, Mode::Alphanumeric) | (_, Mode::Byte) => Some(Ordering::Less),
(Mode::Alphanumeric, Mode::Numeric) | (Mode::Byte, _) => Some(Ordering::Greater),
_ => None,
}
}
}
impl FromStr for Mode {
type Err = EnumParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"numeric" => Ok(Mode::Numeric),
"alphanumeric" => Ok(Mode::Alphanumeric),
"byte" => Ok(Mode::Byte),
"kanji" => Ok(Mode::Kanji),
_ => Err(EnumParseError("expected numeric, alphanumeric, byte or kanji")),
}
}
}
#[cfg(test)]
mod parse_tests {
use std::str::FromStr;
use crate::types::{EcLevel, EnumParseError, Mode, Version};
#[test]
fn test_ec_level_from_str() {
assert_eq!(EcLevel::from_str("L"), Ok(EcLevel::L));
assert_eq!(EcLevel::from_str("q"), Ok(EcLevel::Q));
assert_eq!(EcLevel::from_str(" H "), Ok(EcLevel::H));
assert_eq!(EcLevel::from_str("X"), Err(EnumParseError("expected one of L, M, Q, H")));
}
#[test]
fn test_version_from_str() {
assert_eq!(Version::from_str("1"), Ok(Version::Normal(1)));
assert_eq!(Version::from_str("40"), Ok(Version::Normal(40)));
assert_eq!(Version::from_str("m3"), Ok(Version::Micro(3)));
assert!(Version::from_str("0").is_err());
assert!(Version::from_str("41").is_err());
assert!(Version::from_str("M5").is_err());
assert!(Version::from_str("abc").is_err());
}
#[test]
fn test_mode_from_str() {
assert_eq!(Mode::from_str("Numeric"), Ok(Mode::Numeric));
assert_eq!(Mode::from_str("ALPHANUMERIC"), Ok(Mode::Alphanumeric));
assert_eq!(Mode::from_str(" kanji "), Ok(Mode::Kanji));
assert!(Mode::from_str("text").is_err());
}
}
#[cfg(test)]
mod mode_tests {
use crate::types::Mode::{Alphanumeric, Byte, Kanji, Numeric};
#[test]
fn test_mode_order() {
assert!(Numeric < Alphanumeric);
assert!(Byte > Kanji);
assert!(Numeric.partial_cmp(&Kanji).is_none());
}
#[test]
fn test_max() {
assert_eq!(Byte.max(Kanji), Byte);
assert_eq!(Numeric.max(Alphanumeric), Alphanumeric);
assert_eq!(Alphanumeric.max(Alphanumeric), Alphanumeric);
assert_eq!(Numeric.max(Kanji), Byte);
assert_eq!(Kanji.max(Numeric), Byte);
assert_eq!(Alphanumeric.max(Numeric), Alphanumeric);
assert_eq!(Kanji.max(Kanji), Kanji);
}
}