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//}}}