code128/lib.rs
1//! This crate implements encoding and decoding of Code 128 linear barcodes
2//! as defined in ISO/IEC 15417:2007.
3//!
4//! To achieve a minimal encoding size a dynamic programming approach is used.
5//! The full 256 bit range can be encoded. For compatibility it is recommended
6//! to stay within printable ASCII though.
7//!
8//! ## Example
9//!
10//! ```rust
11//! use code128::{Code128, bars_to_blocks};
12//!
13//! let code = Code128::encode(b"Hello!");
14//! println!("{}", bars_to_blocks(code.bars()));
15//! ```
16//! To create other outputs check out the [Code128] documentation.
17//!
18//! ## Charsets
19//!
20//! For best compatibility it is recommended to only encode printable
21//! [ASCII](https://en.wikipedia.org/wiki/ASCII#Character_set)
22//! characters, i.e. `0x20` (space) to `0x7E` (tilde).
23//!
24//! Code 128 interprets the range `0x00` to `0x7F` as ASCII, and `0xA0` to
25//! `0xFF` as [ISO/IEC 8859-1](https://en.wikipedia.org/wiki/ISO/IEC_8859-1)
26//! (Latin 1). The remaining range from `0x80` to `0x9F` can also be encoded
27//! but has no visual representation.
28//!
29//! In the real world most implementations only handle `0x00` to `0x7F`, or
30//! interpret results as UTF-8, although that is not covered by the standard.
31//! However, if you are in control of encoding and decoding this is technically
32//! possible and maybe even a contemporary choice.
33#![no_std]
34
35#[cfg(not(feature = "std"))]
36extern crate alloc as std;
37#[cfg(feature = "std")]
38extern crate std;
39
40#[cfg(test)]
41use std::vec;
42use std::vec::Vec;
43
44mod decode;
45mod encode;
46mod latin1;
47#[cfg(feature = "unicode")]
48mod unicode;
49
50pub use decode::{decode, decode_str, DecodingError};
51
52#[cfg(feature = "unicode")]
53pub use unicode::bars_to_blocks;
54
55const SHIFT_MODE: u8 = 98;
56const SWITCH_C: u8 = 99;
57const SWITCH_B: u8 = 100;
58const SWITCH_A: u8 = 101;
59const START_A: u8 = 103;
60const START_B: u8 = 104;
61const START_C: u8 = 105;
62const STOP: u8 = 108;
63
64fn checksum(symbols: impl Iterator<Item = u8>) -> u8 {
65 (symbols
66 .enumerate()
67 .map(|(i, idx)| (i.max(1) as u64) * idx as u64)
68 .sum::<u64>()
69 % 103) as u8
70}
71
72/// Representation of a "black line" in the code.
73#[derive(PartialEq, Debug, Clone, Copy)]
74pub struct Bar {
75 /// The width of the line.
76 ///
77 /// Ranges from one to four.
78 pub width: u8,
79 /// White space after the line.
80 pub space: u8,
81}
82
83/// A coordinate of a bar in a barcode.
84#[derive(PartialEq, Debug, Clone, Copy)]
85pub struct BarCoordinate {
86 /// The x coordinate, started from the left.
87 ///
88 /// The first bar will always be at 10, which is the
89 /// offset for the left quiet zone.
90 pub x: u32,
91 /// The width of the bar.
92 pub width: u8,
93}
94
95/// A Code 128.
96///
97/// You can use the bars iterators [`bars()`](Self::bars) or
98/// [`bar_coordinates()`](Self::bar_coordinates), and the [size](Self::len)
99/// to compute a visualization. A bars corresponds to a "black
100/// line" of the code and has a unitless width between one and four, as well
101/// as a free space after it, also sized between one and four.
102///
103/// A bar with width one is called a "module". You can say a bar consists of one
104/// or more modules. The width of a module is often called "X", denoting one
105/// unit in the "x-dimension" of the barcode. A multiple of a module's width is
106/// usually written `<multiplier>X`, for example `1X` or `2.5X`.
107///
108/// ## Pseudo code for visualization
109///
110/// The standard demands a quiet zone of size 10X (see above for notation) on
111/// the left and right side of the code. To compute the size of a bar, multiply
112/// its width with the available space for the code divided by
113/// the [code's length](Self::len).
114///
115/// ```rust
116/// # use code128::Code128;
117/// let code = Code128::encode(b"Code128 <3");
118/// let available_space = 100.0; // unit is, say, "pt"
119/// let line_width = available_space / code.len() as f64;
120/// for bar in code.bar_coordinates() {
121/// let x = bar.x as f64 * line_width;
122/// let width = bar.width as f64 * line_width;
123/// // print line at `x` pt, `width` pt wide
124/// }
125/// ```
126pub struct Code128 {
127 indices: Vec<u8>,
128}
129
130impl Code128 {
131 /// Encode the bytes as Code 128.
132 ///
133 /// See the [module documentation](crate) for hints on charsets.
134 pub fn encode(data: &[u8]) -> Self {
135 Code128Builder::default().encode(data)
136 }
137
138 /// Encode the string as Code 128 using Latin 1.
139 ///
140 /// The functions returns `None` if the string includes characters not
141 /// included in Latin 1.
142 ///
143 /// The control characters of ASCII, `0x00` to `0x19`, are also encoded.
144 pub fn encode_str(text: &str) -> Option<Self> {
145 Code128Builder::default().encode_str(text)
146 }
147
148 /// Get the sequence of bars this Code 128 consists of.
149 pub fn bars(&self) -> impl Iterator<Item = Bar> + '_ {
150 self.indices
151 .iter()
152 .flat_map(|idx| encode::bits_to_bars(encode::PATTERNS[*idx as usize]))
153 }
154
155 /// Get the coordinates of the bars this Code 128 consists of.
156 pub fn bar_coordinates(&self) -> impl Iterator<Item = BarCoordinate> + '_ {
157 self.bars().scan(10, |pos, bar| {
158 let x = *pos;
159 *pos += bar.width as u32 + bar.space as u32;
160 Some(BarCoordinate {
161 x,
162 width: bar.width,
163 })
164 })
165 }
166
167 /// Get the total width of the code in units of the [Bar]
168 /// with the quiet zone included.
169 pub fn len(&self) -> usize {
170 self.indices.len() * 11 + 2 + 20
171 }
172
173 /// Whether this Code 128 encodes empty data.
174 pub fn is_empty(&self) -> bool {
175 self.indices.len() == 3
176 }
177}
178
179#[doc(hidden)]
180#[derive(Copy, Clone)]
181pub enum Encoder {
182 Mixed,
183 DynamicProgramming,
184}
185
186/// Builder for encoding a Code 128 with more control.
187#[doc(hidden)]
188pub struct Code128Builder {
189 encoder: Encoder,
190}
191
192impl Code128Builder {
193 /// Which encoding should be used.
194 pub fn with_encoder(self, encoder: Encoder) -> Self {
195 Self { encoder }
196 }
197
198 /// Encode the bytes as Code 128.
199 ///
200 /// See the [module documentation](crate) for hints on charsets.
201 pub fn encode(self, data: &[u8]) -> Code128 {
202 let mut indices = match self.encoder {
203 Encoder::Mixed => encode::encode_as_indices(data),
204 Encoder::DynamicProgramming => encode::encode_as_indices_dp(data, Vec::new()),
205 };
206 indices.push(checksum(indices.iter().cloned()));
207 indices.push(STOP);
208 Code128 { indices }
209 }
210
211 /// Encode the string as Code 128 using Latin 1.
212 ///
213 /// The functions returns `None` if the string includes characters not
214 /// included in Latin 1.
215 ///
216 /// The control characters of ASCII, `0x00` to `0x19`, are also encoded.
217 pub fn encode_str(self, text: &str) -> Option<Code128> {
218 latin1::utf8_to_latin1(text).map(|data| self.encode(&data))
219 }
220}
221
222#[allow(clippy::derivable_impls)]
223impl Default for Code128Builder {
224 fn default() -> Self {
225 Self {
226 encoder: Encoder::Mixed,
227 }
228 }
229}
230
231#[test]
232fn test_bar_size() {
233 for pattern in &encode::PATTERNS[0..107] {
234 let size: u32 = encode::bits_to_bars(*pattern)
235 .into_iter()
236 .map(|m| m.width as u32 + m.space as u32)
237 .sum();
238 assert_eq!(size, 11);
239 }
240
241 let size: u32 = encode::bits_to_bars(encode::PATTERNS[STOP as usize])
242 .into_iter()
243 .map(|m| m.width as u32 + m.space as u32)
244 .sum();
245 assert_eq!(size, 13);
246}
247
248#[test]
249fn test_code_size() {
250 let code = Code128::encode(b"foo");
251 let size = code
252 .bars()
253 .map(|m| m.width as u32 + m.space as u32)
254 .sum::<u32>()
255 + 20;
256 assert_eq!(code.len(), size as usize);
257}
258
259#[test]
260fn test_is_empty() {
261 assert!(Code128::encode(b"").is_empty());
262 assert!(!Code128::encode(b".").is_empty());
263}
264
265#[test]
266fn test_256() {
267 for x in 0..=255 {
268 let code = Code128::encode(&[x]);
269 let bars: Vec<Bar> = code.bars().collect();
270 assert_eq!(decode(&bars), Ok(vec![x]));
271 }
272}
273
274#[test]
275fn test_string_encoding_example() {
276 let code = Code128::encode_str("Füße").unwrap();
277 let bars: Vec<_> = code.bars().collect();
278 assert_eq!(decode_str(&bars), Ok("Füße".into()));
279}
280
281#[test]
282fn test_bar_coordinates() {
283 let code = Code128::encode(b"");
284 let bars: Vec<_> = code.bar_coordinates().collect();
285 assert_eq!(bars[0], BarCoordinate { x: 10, width: 2 });
286 assert_eq!(bars[1], BarCoordinate { x: 13, width: 1 });
287 assert_eq!(bars[2], BarCoordinate { x: 16, width: 1 });
288 assert_eq!(bars[3], BarCoordinate { x: 21, width: 2 });
289 assert_eq!(bars[4], BarCoordinate { x: 25, width: 2 });
290 assert_eq!(bars[5], BarCoordinate { x: 28, width: 2 });
291 assert_eq!(bars[6], BarCoordinate { x: 32, width: 2 });
292 assert_eq!(bars[7], BarCoordinate { x: 37, width: 3 });
293 assert_eq!(bars[8], BarCoordinate { x: 41, width: 1 });
294 assert_eq!(bars[9], BarCoordinate { x: 43, width: 2 });
295 assert_eq!(bars.len(), 10);
296}