Skip to main content

qrcode_rs/
types.rs

1use crate::cast::As;
2use std::cmp::{Ordering, PartialOrd};
3use std::fmt::{Display, Error, Formatter};
4use std::ops::Not;
5use std::str::FromStr;
6
7//------------------------------------------------------------------------------
8//{{{ QrResult
9
10/// `QrError` encodes the error encountered when generating a QR code.
11///
12/// This enum is `#[non_exhaustive]`: future versions may add variants, so
13/// external callers should match with a `_` arm.
14#[non_exhaustive]
15#[derive(Debug, PartialEq, Eq, Copy, Clone)]
16pub enum QrError {
17    /// The data is too long to encode into a QR code for the given version.
18    DataTooLong,
19
20    /// The provided version / error correction level combination is invalid.
21    /// Carries the offending [`Version`] and [`EcLevel`].
22    InvalidVersion { version: Version, ec_level: EcLevel },
23
24    /// Some characters in the data cannot be supported by the provided QR code
25    /// version.
26    UnsupportedCharacterSet,
27
28    /// The provided ECI designator is invalid. Carries the offending `value`;
29    /// a valid designator must be between 0 and 999999.
30    InvalidEciDesignator { value: u32 },
31
32    /// A character not belonging to the character set is found. Carries the
33    /// byte `position` (offset into the input) and the offending `byte` value.
34    InvalidCharacter { position: usize, byte: u8 },
35}
36
37impl Display for QrError {
38    fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
39        match self {
40            QrError::DataTooLong => fmt.write_str("data too long to encode"),
41            QrError::InvalidVersion { version, ec_level } => {
42                write!(fmt, "invalid version {version:?} for error correction level {ec_level:?}")
43            }
44            QrError::UnsupportedCharacterSet => fmt.write_str("unsupported character set for this version"),
45            QrError::InvalidEciDesignator { value } => {
46                write!(fmt, "invalid ECI designator {value} (must be 0..=999999)")
47            }
48            QrError::InvalidCharacter { position, byte } => {
49                write!(fmt, "invalid character byte 0x{byte:02x} at position {position}")
50            }
51        }
52    }
53}
54
55impl ::std::error::Error for QrError {}
56
57/// `QrResult` is a convenient alias for a QR code generation result.
58pub type QrResult<T> = Result<T, QrError>;
59
60//}}}
61//------------------------------------------------------------------------------
62//{{{ Enum parsing error
63
64/// Error returned when parsing an enum (`EcLevel`, `Version`, `Mode`) from a
65/// string via `FromStr`. Carries a short static description of what was
66/// expected.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct EnumParseError(pub &'static str);
69
70impl Display for EnumParseError {
71    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
72        f.write_str(self.0)
73    }
74}
75
76impl ::std::error::Error for EnumParseError {}
77
78//}}}
79//------------------------------------------------------------------------------
80//{{{ Color
81
82/// The color of a module.
83#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
84pub enum Color {
85    /// The module is light colored.
86    Light,
87    /// The module is dark colored.
88    Dark,
89}
90
91impl Color {
92    /// Selects a value according to color of the module. Equivalent to
93    /// `if self != Color::Light { dark } else { light }`.
94    ///
95    /// # Examples
96    ///
97    /// ```rust
98    /// # use qrcode_rs::types::Color;
99    /// assert_eq!(Color::Light.select(1, 0), 0);
100    /// assert_eq!(Color::Dark.select("black", "white"), "black");
101    /// ```
102    pub fn select<T>(self, dark: T, light: T) -> T {
103        match self {
104            Color::Light => light,
105            Color::Dark => dark,
106        }
107    }
108}
109
110impl Not for Color {
111    type Output = Self;
112    fn not(self) -> Self {
113        match self {
114            Color::Light => Color::Dark,
115            Color::Dark => Color::Light,
116        }
117    }
118}
119
120//}}}
121//------------------------------------------------------------------------------
122//{{{ Error correction level
123
124/// The error correction level. It allows the original information be recovered
125/// even if parts of the code is damaged.
126#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
127pub enum EcLevel {
128    /// Low error correction. Allows up to 7% of wrong blocks.
129    L = 0,
130
131    /// Medium error correction (default). Allows up to 15% of wrong blocks.
132    M = 1,
133
134    /// "Quartile" error correction. Allows up to 25% of wrong blocks.
135    Q = 2,
136
137    /// High error correction. Allows up to 30% of wrong blocks.
138    H = 3,
139}
140
141impl FromStr for EcLevel {
142    type Err = EnumParseError;
143
144    /// Parses a case-insensitive single letter: `L`, `M`, `Q` or `H`.
145    ///
146    /// # Examples
147    ///
148    /// ```rust
149    /// use std::str::FromStr;
150    /// use qrcode_rs::types::EcLevel;
151    /// assert_eq!(EcLevel::from_str("H"), Ok(EcLevel::H));
152    /// assert_eq!(EcLevel::from_str("m"), Ok(EcLevel::M));
153    /// assert!(EcLevel::from_str("X").is_err());
154    /// ```
155    fn from_str(s: &str) -> Result<Self, Self::Err> {
156        match s.trim() {
157            "l" | "L" => Ok(EcLevel::L),
158            "m" | "M" => Ok(EcLevel::M),
159            "q" | "Q" => Ok(EcLevel::Q),
160            "h" | "H" => Ok(EcLevel::H),
161            _ => Err(EnumParseError("expected one of L, M, Q, H")),
162        }
163    }
164}
165
166//}}}
167//------------------------------------------------------------------------------
168//{{{ Version
169
170/// In QR code terminology, `Version` means the size of the generated image.
171/// Larger version means the size of code is larger, and therefore can carry
172/// more information.
173///
174/// The smallest version is `Version::Normal(1)` of size 21×21, and the largest
175/// is `Version::Normal(40)` of size 177×177.
176#[derive(Debug, PartialEq, Eq, Copy, Clone)]
177pub enum Version {
178    /// A normal QR code version. The parameter should be between 1 and 40.
179    Normal(i16),
180
181    /// A Micro QR code version. The parameter should be between 1 and 4.
182    Micro(i16),
183}
184
185impl Version {
186    /// Get the number of "modules" on each size of the QR code, i.e. the width
187    /// and height of the code.
188    pub const fn width(self) -> i16 {
189        match self {
190            Version::Normal(v) => v * 4 + 17,
191            Version::Micro(v) => v * 2 + 9,
192        }
193    }
194
195    /// Obtains an object from a hard-coded table.
196    ///
197    /// The table must be a 44×4 array. The outer array represents the content
198    /// for each version. The first 40 entry corresponds to QR code versions 1
199    /// to 40, and the last 4 corresponds to Micro QR code version 1 to 4. The
200    /// inner array represents the content in each error correction level, in
201    /// the order [L, M, Q, H].
202    ///
203    /// # Errors
204    ///
205    /// If the entry compares equal to the default value of `T`, this method
206    /// returns `Err(QrError::InvalidVersion)`.
207    pub fn fetch<T>(self, ec_level: EcLevel, table: &[[T; 4]]) -> QrResult<T>
208    where
209        T: PartialEq + Default + Copy,
210    {
211        match self {
212            Version::Normal(v @ 1..=40) => {
213                return Ok(table[(v - 1).as_usize()][ec_level as usize]);
214            }
215            Version::Micro(v @ 1..=4) => {
216                let obj = table[(v + 39).as_usize()][ec_level as usize];
217                if obj != T::default() {
218                    return Ok(obj);
219                }
220            }
221            _ => {}
222        }
223        Err(QrError::InvalidVersion { version: self, ec_level })
224    }
225
226    /// The number of bits needed to encode the mode indicator.
227    pub fn mode_bits_count(self) -> usize {
228        if let Version::Micro(a) = self { (a - 1).as_usize() } else { 4 }
229    }
230
231    /// Checks whether is version refers to a Micro QR code.
232    pub fn is_micro(self) -> bool {
233        matches!(self, Version::Micro(_))
234    }
235}
236
237impl FromStr for Version {
238    type Err = EnumParseError;
239
240    /// Parses a normal QR version `1..=40` (e.g. `"5"` → `Normal(5)`) or a
241    /// Micro QR version `M1..M4` (e.g. `"m2"` → `Micro(2)`), case-insensitively.
242    ///
243    /// # Examples
244    ///
245    /// ```rust
246    /// use std::str::FromStr;
247    /// use qrcode_rs::types::Version;
248    /// assert_eq!(Version::from_str("5"), Ok(Version::Normal(5)));
249    /// assert_eq!(Version::from_str("M4"), Ok(Version::Micro(4)));
250    /// assert!(Version::from_str("99").is_err());
251    /// ```
252    fn from_str(s: &str) -> Result<Self, Self::Err> {
253        let s = s.trim();
254        let (micro, digits) = match s.chars().next() {
255            Some('M' | 'm') => (true, &s[1..]),
256            _ => (false, s),
257        };
258        let n: i16 = digits.parse().map_err(|_| EnumParseError("expected a version number"))?;
259        if micro {
260            if (1..=4).contains(&n) {
261                Ok(Version::Micro(n))
262            } else {
263                Err(EnumParseError("Micro QR version must be between M1 and M4"))
264            }
265        } else if (1..=40).contains(&n) {
266            Ok(Version::Normal(n))
267        } else {
268            Err(EnumParseError("QR version must be between 1 and 40"))
269        }
270    }
271}
272
273//}}}
274//------------------------------------------------------------------------------
275//{{{ Mode indicator
276
277/// The mode indicator, which specifies the character set of the encoded data.
278#[derive(Debug, PartialEq, Eq, Copy, Clone)]
279pub enum Mode {
280    /// The data contains only characters 0 to 9.
281    Numeric,
282
283    /// The data contains only uppercase letters (A–Z), numbers (0–9) and a few
284    /// punctuations marks (space, `$`, `%`, `*`, `+`, `-`, `.`, `/`, `:`).
285    Alphanumeric,
286
287    /// The data contains arbitrary binary data.
288    Byte,
289
290    /// The data contains Shift-JIS-encoded double-byte text.
291    Kanji,
292}
293
294impl Mode {
295    /// Computes the number of bits needed to encode the data length.
296    ///
297    ///     use qrcode_rs::types::{Version, Mode};
298    ///
299    ///     assert_eq!(Mode::Numeric.length_bits_count(Version::Normal(1)), 10);
300    ///
301    /// This method will return `Err(QrError::UnsupportedCharacterSet)` if the
302    /// mode is not supported in the given version.
303    pub fn length_bits_count(self, version: Version) -> usize {
304        match version {
305            Version::Micro(a) => {
306                let a = a.as_usize();
307                match self {
308                    Mode::Numeric => 2 + a,
309                    Mode::Alphanumeric | Mode::Byte => 1 + a,
310                    Mode::Kanji => a,
311                }
312            }
313            Version::Normal(1..=9) => match self {
314                Mode::Numeric => 10,
315                Mode::Alphanumeric => 9,
316                Mode::Byte | Mode::Kanji => 8,
317            },
318            Version::Normal(10..=26) => match self {
319                Mode::Numeric => 12,
320                Mode::Alphanumeric => 11,
321                Mode::Byte => 16,
322                Mode::Kanji => 10,
323            },
324            Version::Normal(_) => match self {
325                Mode::Numeric => 14,
326                Mode::Alphanumeric => 13,
327                Mode::Byte => 16,
328                Mode::Kanji => 12,
329            },
330        }
331    }
332
333    /// Computes the number of bits needed to some data of a given raw length.
334    ///
335    ///     use qrcode_rs::types::Mode;
336    ///
337    ///     assert_eq!(Mode::Numeric.data_bits_count(7), 24);
338    ///
339    /// Note that in Kanji mode, the `raw_data_len` is the number of Kanjis,
340    /// i.e. half the total size of bytes.
341    pub fn data_bits_count(self, raw_data_len: usize) -> usize {
342        match self {
343            Mode::Numeric => (raw_data_len * 10).div_ceil(3),
344            Mode::Alphanumeric => (raw_data_len * 11).div_ceil(2),
345            Mode::Byte => raw_data_len * 8,
346            Mode::Kanji => raw_data_len * 13,
347        }
348    }
349
350    /// Find the lowest common mode which both modes are compatible with.
351    ///
352    ///     use qrcode_rs::types::Mode;
353    ///
354    ///     let a = Mode::Numeric;
355    ///     let b = Mode::Kanji;
356    ///     let c = a.max(b);
357    ///     assert!(a <= c);
358    ///     assert!(b <= c);
359    ///
360    #[must_use]
361    pub fn max(self, other: Self) -> Self {
362        match self.partial_cmp(&other) {
363            Some(Ordering::Greater) => self,
364            Some(_) => other,
365            None => Mode::Byte,
366        }
367    }
368}
369
370impl PartialOrd for Mode {
371    /// Defines a partial ordering between modes. If `a <= b`, then `b` contains
372    /// a superset of all characters supported by `a`.
373    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
374        match (self, other) {
375            (a, b) if a == b => Some(Ordering::Equal),
376            (Mode::Numeric, Mode::Alphanumeric) | (_, Mode::Byte) => Some(Ordering::Less),
377            (Mode::Alphanumeric, Mode::Numeric) | (Mode::Byte, _) => Some(Ordering::Greater),
378            _ => None,
379        }
380    }
381}
382
383impl FromStr for Mode {
384    type Err = EnumParseError;
385
386    /// Parses a mode by name, case-insensitively: `numeric`, `alphanumeric`,
387    /// `byte` or `kanji`.
388    ///
389    /// # Examples
390    ///
391    /// ```rust
392    /// use std::str::FromStr;
393    /// use qrcode_rs::types::Mode;
394    /// assert_eq!(Mode::from_str("Byte"), Ok(Mode::Byte));
395    /// assert!(Mode::from_str("utf8").is_err());
396    /// ```
397    fn from_str(s: &str) -> Result<Self, Self::Err> {
398        match s.trim().to_ascii_lowercase().as_str() {
399            "numeric" => Ok(Mode::Numeric),
400            "alphanumeric" => Ok(Mode::Alphanumeric),
401            "byte" => Ok(Mode::Byte),
402            "kanji" => Ok(Mode::Kanji),
403            _ => Err(EnumParseError("expected numeric, alphanumeric, byte or kanji")),
404        }
405    }
406}
407
408#[cfg(test)]
409mod parse_tests {
410    use std::str::FromStr;
411
412    use crate::types::{EcLevel, EnumParseError, Mode, Version};
413
414    #[test]
415    fn test_ec_level_from_str() {
416        assert_eq!(EcLevel::from_str("L"), Ok(EcLevel::L));
417        assert_eq!(EcLevel::from_str("q"), Ok(EcLevel::Q));
418        assert_eq!(EcLevel::from_str("  H "), Ok(EcLevel::H));
419        assert_eq!(EcLevel::from_str("X"), Err(EnumParseError("expected one of L, M, Q, H")));
420    }
421
422    #[test]
423    fn test_version_from_str() {
424        assert_eq!(Version::from_str("1"), Ok(Version::Normal(1)));
425        assert_eq!(Version::from_str("40"), Ok(Version::Normal(40)));
426        assert_eq!(Version::from_str("m3"), Ok(Version::Micro(3)));
427        assert!(Version::from_str("0").is_err());
428        assert!(Version::from_str("41").is_err());
429        assert!(Version::from_str("M5").is_err());
430        assert!(Version::from_str("abc").is_err());
431    }
432
433    #[test]
434    fn test_mode_from_str() {
435        assert_eq!(Mode::from_str("Numeric"), Ok(Mode::Numeric));
436        assert_eq!(Mode::from_str("ALPHANUMERIC"), Ok(Mode::Alphanumeric));
437        assert_eq!(Mode::from_str(" kanji "), Ok(Mode::Kanji));
438        assert!(Mode::from_str("text").is_err());
439    }
440}
441
442#[cfg(test)]
443mod mode_tests {
444    use crate::types::Mode::{Alphanumeric, Byte, Kanji, Numeric};
445
446    #[test]
447    fn test_mode_order() {
448        assert!(Numeric < Alphanumeric);
449        assert!(Byte > Kanji);
450        assert!(Numeric.partial_cmp(&Kanji).is_none());
451    }
452
453    #[test]
454    fn test_max() {
455        assert_eq!(Byte.max(Kanji), Byte);
456        assert_eq!(Numeric.max(Alphanumeric), Alphanumeric);
457        assert_eq!(Alphanumeric.max(Alphanumeric), Alphanumeric);
458        assert_eq!(Numeric.max(Kanji), Byte);
459        assert_eq!(Kanji.max(Numeric), Byte);
460        assert_eq!(Alphanumeric.max(Numeric), Alphanumeric);
461        assert_eq!(Kanji.max(Kanji), Kanji);
462    }
463}
464
465//}}}