Skip to main content

barcodes/linear/
code93.rs

1//! Code 93 barcode encoder.
2//!
3//! Code 93 is a variable-length, continuous symbology supporting digits 0–9,
4//! uppercase letters A–Z, space, and the special characters `-`, `.`, `$`,
5//! `/`, `+`, `%` (43 data characters in total).
6//!
7//! Each character is represented by 9 modules composed of 3 bars and 3 spaces.
8//! The symbol is framed by the `*` start/stop character and terminated by a
9//! single final bar module.  Two modulo-47 check characters (C and K) are
10//! appended automatically after the data.
11#![forbid(unsafe_code)]
12
13use crate::common::{
14    buffer::SliceWriter, errors::EncodeError, traits::BarcodeEncoder, types::Encoded,
15};
16
17/// Maximum number of data characters supported in a single symbol.
18const MAX_DATA: usize = 256;
19
20// ---- Encoding table --------------------------------------------------------
21
22/// The 43 encodable data characters, indexed by their Code 93 value (0–42).
23const CODE93_CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";
24
25/// Module patterns indexed by character value.
26///
27/// Indices 0–42 map to [`CODE93_CHARS`], indices 43–46 are the four control
28/// characters (reachable as check-character values), and index 47 is the
29/// `*` start/stop character.  Each pattern is a 9-bit module map where the
30/// most-significant bit is the leftmost module: `1` = dark, `0` = light.
31const CODE93_PATTERNS: [u16; 48] = [
32    0b100010100, // 0
33    0b101001000, // 1
34    0b101000100, // 2
35    0b101000010, // 3
36    0b100101000, // 4
37    0b100100100, // 5
38    0b100100010, // 6
39    0b101010000, // 7
40    0b100010010, // 8
41    0b100001010, // 9
42    0b110101000, // A
43    0b110100100, // B
44    0b110100010, // C
45    0b110010100, // D
46    0b110010010, // E
47    0b110001010, // F
48    0b101101000, // G
49    0b101100100, // H
50    0b101100010, // I
51    0b100110100, // J
52    0b100011010, // K
53    0b101011000, // L
54    0b101001100, // M
55    0b101000110, // N
56    0b100101100, // O
57    0b100010110, // P
58    0b110110100, // Q
59    0b110110010, // R
60    0b110101100, // S
61    0b110100110, // T
62    0b110010110, // U
63    0b110011010, // V
64    0b101101100, // W
65    0b101100110, // X
66    0b100110110, // Y
67    0b100111010, // Z
68    0b100101110, // -
69    0b111010100, // .
70    0b111010010, // (space)
71    0b111001010, // $
72    0b101101110, // /
73    0b101110110, // +
74    0b110101110, // %
75    0b100100110, // ($)  control
76    0b111011010, // (%)  control
77    0b111010110, // (/)  control
78    0b100110010, // (+)  control
79    0b101011110, // *    start/stop
80];
81
82/// Value of the `*` start/stop character.
83const START_STOP: usize = 47;
84
85// ---- Public encoder --------------------------------------------------------
86
87/// Code 93 barcode encoder.
88///
89/// Encodes uppercase alphanumeric text and the special characters `-`, `.`,
90/// `$`, `/`, `+`, `%`, and space.  The `*` start/stop delimiters and the two
91/// modulo-47 check characters are added automatically.
92///
93/// # Example
94///
95/// ```rust
96/// use barcodes::common::traits::BarcodeEncoder;
97/// use barcodes::common::types::Encoded;
98/// use barcodes::linear::code93::Code93;
99///
100/// let mut buf = [false; 256];
101/// let Encoded::Linear { len, .. } = Code93::encode_into("CODE93", &mut buf).unwrap()
102/// else { unreachable!() };
103/// let bars = &buf[..len];
104/// ```
105pub struct Code93;
106
107impl BarcodeEncoder for Code93 {
108    type Input = str;
109
110    fn encode_into(input: &str, buf: &mut [bool]) -> Result<Encoded, EncodeError> {
111        if input.is_empty() {
112            return Err(EncodeError::InvalidInput("Code 93 input must not be empty"));
113        }
114
115        // Map each character to its value in a fixed stack buffer (+2 for C, K).
116        let mut values = [0usize; MAX_DATA + 2];
117        let mut n = 0;
118        for ch in input.chars() {
119            let v = char_value(ch).ok_or(EncodeError::InvalidCharacter(ch))?;
120            if n >= MAX_DATA {
121                return Err(EncodeError::DataTooLong);
122            }
123            values[n] = v;
124            n += 1;
125        }
126
127        // Append the two check characters (C then K).
128        let c = check_value(&values[..n], 20);
129        values[n] = c;
130        n += 1;
131        let k = check_value(&values[..n], 15);
132        values[n] = k;
133        n += 1;
134
135        let len = encode_bars(&values[..n], buf)?;
136        Ok(Encoded::Linear { len, height: 50 })
137    }
138
139    fn symbology_name() -> &'static str {
140        "Code 93"
141    }
142}
143
144// ---- Helpers ---------------------------------------------------------------
145
146/// Return the Code 93 value (0–42) for a data character, if valid.
147fn char_value(ch: char) -> Option<usize> {
148    let byte = u8::try_from(ch as u32).ok()?;
149    CODE93_CHARS.iter().position(|&c| c == byte)
150}
151
152/// Compute a modulo-47 weighted check value over `values`.
153///
154/// Weights run 1, 2, 3, … up to `max_weight` from the rightmost character,
155/// then wrap back to 1.
156fn check_value(values: &[usize], max_weight: u32) -> usize {
157    let mut weight = 1u32;
158    let mut sum = 0u32;
159    for &v in values.iter().rev() {
160        sum += weight * v as u32;
161        weight = if weight == max_weight { 1 } else { weight + 1 };
162    }
163    (sum % 47) as usize
164}
165
166/// Append a character's 9-module pattern to the writer.
167fn append_pattern(w: &mut SliceWriter, value: usize) -> Result<(), EncodeError> {
168    let pattern = CODE93_PATTERNS[value];
169    for i in (0..9).rev() {
170        w.push((pattern >> i) & 1 == 1)?;
171    }
172    Ok(())
173}
174
175/// Write start + data/check chars + stop + termination bar; return module count.
176fn encode_bars(values: &[usize], buf: &mut [bool]) -> Result<usize, EncodeError> {
177    let mut w = SliceWriter::new(buf);
178
179    append_pattern(&mut w, START_STOP)?;
180    for &v in values {
181        append_pattern(&mut w, v)?;
182    }
183    append_pattern(&mut w, START_STOP)?;
184
185    // Final termination bar (single dark module).
186    w.push(true)?;
187
188    Ok(w.len())
189}
190
191// ---- Tests -----------------------------------------------------------------
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    fn encode_len(input: &str) -> usize {
198        let mut buf = [false; 4096];
199        match Code93::encode_into(input, &mut buf).unwrap() {
200            Encoded::Linear { len, .. } => len,
201            _ => panic!("expected linear"),
202        }
203    }
204
205    #[test]
206    fn test_encode_basic() {
207        assert!(encode_len("CODE93") > 0);
208    }
209
210    #[test]
211    fn test_encode_special_chars() {
212        assert!(encode_len("HELLO WORLD") > 0);
213    }
214
215    #[test]
216    fn test_invalid_lowercase() {
217        let mut buf = [false; 4096];
218        assert!(Code93::encode_into("hello", &mut buf).is_err());
219    }
220
221    #[test]
222    fn test_invalid_symbol() {
223        let mut buf = [false; 4096];
224        assert!(Code93::encode_into("ABC!DEF", &mut buf).is_err());
225    }
226
227    #[test]
228    fn test_empty_input() {
229        let mut buf = [false; 4096];
230        assert!(Code93::encode_into("", &mut buf).is_err());
231    }
232
233    #[test]
234    fn test_buffer_too_small() {
235        let mut buf = [false; 4];
236        assert_eq!(
237            Code93::encode_into("CODE93", &mut buf),
238            Err(EncodeError::BufferTooSmall)
239        );
240    }
241
242    #[test]
243    fn test_symbology_name() {
244        assert_eq!(Code93::symbology_name(), "Code 93");
245    }
246
247    #[test]
248    fn test_bar_count() {
249        // "A": start + A + C + K + stop = 5 chars * 9 modules + 1 termination.
250        assert_eq!(encode_len("A"), 5 * 9 + 1);
251    }
252
253    #[test]
254    fn test_check_values_known() {
255        // Worked example: "CODE93" -> C check char 'P' (25), K check char 'V' (31).
256        let mut values = [0usize; 8];
257        let mut n = 0;
258        for ch in "CODE93".chars() {
259            values[n] = char_value(ch).unwrap();
260            n += 1;
261        }
262        let c = check_value(&values[..n], 20);
263        assert_eq!(c, char_value('P').unwrap());
264        values[n] = c;
265        n += 1;
266        let k = check_value(&values[..n], 15);
267        assert_eq!(k, char_value('V').unwrap());
268    }
269
270    #[cfg(feature = "alloc")]
271    #[test]
272    fn test_svg_output() {
273        let svg = Code93::encode("TEST").unwrap().to_svg_string();
274        assert!(svg.starts_with("<svg "));
275    }
276}