1use crate::Mode;
8
9pub trait EncodingMode {
11 const MODE: Mode;
13
14 fn validate(data: &[u8]) -> bool;
16
17 fn invalid_character(data: &[u8]) -> Option<(usize, u8)>;
19
20 fn character_count(data: &[u8]) -> usize {
25 data.len()
26 }
27
28 fn bits_per_character() -> f64;
30}
31
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
34pub struct NumericMode;
35
36impl EncodingMode for NumericMode {
37 const MODE: Mode = Mode::Numeric;
38
39 fn validate(data: &[u8]) -> bool {
40 data.iter().all(u8::is_ascii_digit)
41 }
42
43 fn invalid_character(data: &[u8]) -> Option<(usize, u8)> {
44 data.iter().copied().enumerate().find(|(_, byte)| !byte.is_ascii_digit())
45 }
46
47 fn bits_per_character() -> f64 {
48 10.0 / 3.0
49 }
50}
51
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
54pub struct AlphanumericMode;
55
56impl EncodingMode for AlphanumericMode {
57 const MODE: Mode = Mode::Alphanumeric;
58
59 fn validate(data: &[u8]) -> bool {
60 data.iter().copied().all(is_alphanumeric)
61 }
62
63 fn invalid_character(data: &[u8]) -> Option<(usize, u8)> {
64 data.iter().copied().enumerate().find(|(_, byte)| !is_alphanumeric(*byte))
65 }
66
67 fn bits_per_character() -> f64 {
68 11.0 / 2.0
69 }
70}
71
72#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
74pub struct ByteMode;
75
76impl EncodingMode for ByteMode {
77 const MODE: Mode = Mode::Byte;
78
79 fn validate(_data: &[u8]) -> bool {
80 true
81 }
82
83 fn invalid_character(_data: &[u8]) -> Option<(usize, u8)> {
84 None
85 }
86
87 fn bits_per_character() -> f64 {
88 8.0
89 }
90}
91
92#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
94pub struct KanjiMode;
95
96impl EncodingMode for KanjiMode {
97 const MODE: Mode = Mode::Kanji;
98
99 fn validate(data: &[u8]) -> bool {
100 Self::invalid_character(data).is_none()
101 }
102
103 fn invalid_character(data: &[u8]) -> Option<(usize, u8)> {
104 for (index, chunk) in data.chunks(2).enumerate() {
105 if chunk.len() != 2 {
106 return Some((index * 2, chunk[0]));
107 }
108 let hi = chunk[0];
109 let lo = chunk[1];
110 let codepoint = u16::from(hi) << 8 | u16::from(lo);
111 if !is_kanji_codepoint(codepoint) {
112 return Some((index * 2, hi));
113 }
114 }
115 None
116 }
117
118 fn character_count(data: &[u8]) -> usize {
119 data.len() / 2
120 }
121
122 fn bits_per_character() -> f64 {
123 13.0
124 }
125}
126
127fn is_alphanumeric(byte: u8) -> bool {
128 matches!(
129 byte,
130 b'0'..=b'9' | b'A'..=b'Z' | b' ' | b'$' | b'%' | b'*' | b'+' | b'-' | b'.' | b'/' | b':'
131 )
132}
133
134fn is_kanji_codepoint(codepoint: u16) -> bool {
135 (0x8140..=0x9ffc).contains(&codepoint) || (0xe040..=0xebbf).contains(&codepoint)
136}
137
138#[cfg(test)]
139mod tests {
140 use super::{AlphanumericMode, ByteMode, EncodingMode, KanjiMode, NumericMode};
141 use crate::Mode;
142
143 #[test]
144 fn numeric_mode_validates_ascii_digits() {
145 assert!(NumericMode::validate(b"0123456789"));
146 assert_eq!(NumericMode::invalid_character(b"12a"), Some((2, b'a')));
147 assert_eq!(NumericMode::MODE, Mode::Numeric);
148 }
149
150 #[test]
151 fn alphanumeric_mode_validates_qr_alphabet() {
152 assert!(AlphanumericMode::validate(b"HELLO WORLD-42"));
153 assert_eq!(AlphanumericMode::invalid_character(b"hello"), Some((0, b'h')));
154 assert_eq!(AlphanumericMode::MODE, Mode::Alphanumeric);
155 }
156
157 #[test]
158 fn byte_mode_accepts_any_bytes() {
159 assert!(ByteMode::validate(&[0, b'a', 255]));
160 assert_eq!(ByteMode::invalid_character(&[0, b'a', 255]), None);
161 assert_eq!(ByteMode::MODE, Mode::Byte);
162 }
163
164 #[test]
165 fn kanji_mode_validates_shift_jis_pairs() {
166 assert!(KanjiMode::validate(b"\x93\x5f\xe4\xaa"));
167 assert_eq!(KanjiMode::character_count(b"\x93\x5f\xe4\xaa"), 2);
168 assert_eq!(KanjiMode::invalid_character(b"\x93"), Some((0, 0x93)));
169 assert_eq!(KanjiMode::invalid_character(b"\x01\x02"), Some((0, 0x01)));
170 assert_eq!(KanjiMode::MODE, Mode::Kanji);
171 }
172}