imprimatur 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/Cargo.lock +33 -3
  4. data/Cargo.toml +1 -1
  5. data/README.md +48 -1
  6. data/ext/imprimatur_native/cli/Cargo.toml +3 -1
  7. data/ext/imprimatur_native/core/Cargo.toml +15 -4
  8. data/ext/imprimatur_native/core/README.md +27 -4
  9. data/ext/imprimatur_native/core/src/barcode/code128.rs +162 -0
  10. data/ext/imprimatur_native/core/src/barcode/draw.rs +124 -0
  11. data/ext/imprimatur_native/core/src/barcode/ean.rs +91 -0
  12. data/ext/imprimatur_native/core/src/barcode/gs1.rs +188 -0
  13. data/ext/imprimatur_native/core/src/barcode/matrix.rs +130 -0
  14. data/ext/imprimatur_native/core/src/barcode.rs +314 -0
  15. data/ext/imprimatur_native/core/src/doc/symbol.rs +136 -0
  16. data/ext/imprimatur_native/core/src/doc.rs +11 -1
  17. data/ext/imprimatur_native/core/src/docx.rs +22 -10
  18. data/ext/imprimatur_native/core/src/error.rs +6 -0
  19. data/ext/imprimatur_native/core/src/font.rs +3 -1
  20. data/ext/imprimatur_native/core/src/lib.rs +3 -1
  21. data/ext/imprimatur_native/core/src/pdf/layout.rs +48 -14
  22. data/ext/imprimatur_native/core/src/pdf/truetype/descriptor.rs +129 -0
  23. data/ext/imprimatur_native/core/src/pdf/truetype/program.rs +70 -0
  24. data/ext/imprimatur_native/core/src/pdf/truetype.rs +149 -0
  25. data/ext/imprimatur_native/core/src/pdf.rs +9 -1
  26. data/ext/imprimatur_native/core/src/read/image.rs +17 -0
  27. data/ext/imprimatur_native/core/src/read.rs +3 -2
  28. data/ext/imprimatur_native/core/src/style.rs +11 -0
  29. data/ext/imprimatur_native/ffi/Cargo.toml +1 -1
  30. data/ext/imprimatur_native/ffi/src/barcode.rs +147 -0
  31. data/ext/imprimatur_native/ffi/src/document.rs +29 -1
  32. data/ext/imprimatur_native/ffi/src/lib.rs +5 -3
  33. data/lib/imprimatur/barcode.rb +78 -0
  34. data/lib/imprimatur/document.rb +17 -1
  35. data/lib/imprimatur/reader.rb +48 -11
  36. data/lib/imprimatur/spreadsheet.rb +1 -1
  37. data/lib/imprimatur/version.rb +1 -1
  38. data/lib/imprimatur.rb +6 -1
  39. metadata +13 -2
  40. data/ext/imprimatur_native/core/src/test_support.rs +0 -251
@@ -0,0 +1,91 @@
1
+ //! EAN-13, EAN-8 and UPC-A (ISO/IEC 15420): GTINs as retail scanners read
2
+ //! them, digits in seven-module patterns between guard bars.
3
+
4
+ use super::Symbology;
5
+ use super::gs1::check_digit;
6
+ use crate::Result;
7
+
8
+ /// The left-hand, odd-parity patterns (set A) of 0 to 9. The right-hand
9
+ /// patterns (set C) are their inverses; the even-parity ones (set B), the
10
+ /// right-hand ones mirrored.
11
+ const SET_A: [u8; 10] = [
12
+ 0b000_1101, 0b001_1001, 0b001_0011, 0b011_1101, 0b010_0011, 0b011_0001, 0b010_1111, 0b011_1011,
13
+ 0b011_0111, 0b000_1011,
14
+ ];
15
+ /// Which of EAN-13's left six digits take set B, the first in the high
16
+ /// bit, by its leading digit (the one no pattern draws).
17
+ const PARITY: [u8; 10] = [
18
+ 0b00_0000, 0b00_1011, 0b00_1101, 0b00_1110, 0b01_0011, 0b01_1001, 0b01_1100, 0b01_0101,
19
+ 0b01_0110, 0b01_1010,
20
+ ];
21
+ const GUARD: [bool; 3] = [true, false, true];
22
+ const CENTER: [bool; 5] = [false, true, false, true, false];
23
+
24
+ /// `input` as digits: one short of `length` gets its check digit, `length`
25
+ /// must end with the right one.
26
+ pub fn digits(symbology: Symbology, input: &str, length: usize) -> Result<Vec<u8>> {
27
+ input
28
+ .bytes()
29
+ .all(|byte| byte.is_ascii_digit())
30
+ .ok_or_else(|| symbology.invalid(format!("{input:?} is not all digits")))?;
31
+ (input.len() == length - 1 || input.len() == length).ok_or_else(|| {
32
+ let (short, given) = (length - 1, input.len());
33
+ symbology.invalid(format!(
34
+ "it takes {short} digits, or {length} with the check digit, not {given}"
35
+ ))
36
+ })?;
37
+ let check = check_digit(&input[..length - 1]).unwrap_or_default();
38
+ let mut digits: Vec<u8> = input.bytes().map(|byte| byte - b'0').collect();
39
+ match digits.get(length - 1) {
40
+ None => digits.push(check),
41
+ Some(&last) if last != check => {
42
+ return Err(symbology.invalid(format!("{input} has check digit {last}, not {check}")));
43
+ }
44
+ Some(_) => {}
45
+ }
46
+ Ok(digits)
47
+ }
48
+
49
+ pub fn text(digits: &[u8]) -> String {
50
+ digits.iter().map(|digit| char::from(b'0' + digit)).collect()
51
+ }
52
+
53
+ /// 95 modules: guard, six left digits by the first's parity, center, six
54
+ /// right digits, guard.
55
+ pub fn ean13(digits: &[u8]) -> Vec<bool> {
56
+ let parity = PARITY[usize::from(digits[0])];
57
+ let mut modules = Vec::with_capacity(95);
58
+ modules.extend(GUARD);
59
+ for (index, &digit) in digits[1..7].iter().enumerate() {
60
+ let even = (parity >> (5 - index)) & 1 == 1;
61
+ push(&mut modules, if even { set_b(digit) } else { SET_A[usize::from(digit)] });
62
+ }
63
+ modules.extend(CENTER);
64
+ digits[7..].iter().for_each(|&digit| push(&mut modules, set_c(digit)));
65
+ modules.extend(GUARD);
66
+ modules
67
+ }
68
+
69
+ /// 67 modules: guard, four digits in set A, center, four in set C, guard.
70
+ pub fn ean8(digits: &[u8]) -> Vec<bool> {
71
+ let mut modules = Vec::with_capacity(67);
72
+ modules.extend(GUARD);
73
+ digits[..4].iter().for_each(|&digit| push(&mut modules, SET_A[usize::from(digit)]));
74
+ modules.extend(CENTER);
75
+ digits[4..].iter().for_each(|&digit| push(&mut modules, set_c(digit)));
76
+ modules.extend(GUARD);
77
+ modules
78
+ }
79
+
80
+ const fn set_c(digit: u8) -> u8 {
81
+ !SET_A[digit as usize] & 0x7F
82
+ }
83
+
84
+ const fn set_b(digit: u8) -> u8 {
85
+ set_c(digit).reverse_bits() >> 1
86
+ }
87
+
88
+ /// A seven-module pattern, its high bit first.
89
+ fn push(modules: &mut Vec<bool>, pattern: u8) {
90
+ modules.extend((0..7).rev().map(|bit| (pattern >> bit) & 1 == 1));
91
+ }
@@ -0,0 +1,188 @@
1
+ //! GS1 element strings as people write them, `(01)09506000134352(10)AB12`:
2
+ //! Application Identifiers in parentheses, each followed by its value.
3
+
4
+ #[cfg(test)]
5
+ mod tests;
6
+
7
+ use crate::{Error, Result};
8
+
9
+ /// What a symbol carries: characters, and FNC1, which ends every
10
+ /// variable-length field but the last.
11
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
12
+ pub enum Unit {
13
+ Char(u8),
14
+ Fnc1,
15
+ }
16
+
17
+ /// One Application Identifier and its value.
18
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19
+ pub struct Field<'a> {
20
+ pub ai: &'a str,
21
+ pub value: &'a str,
22
+ }
23
+
24
+ /// Element strings whose length, AI included, the AI's first two digits
25
+ /// fix (GS1 General Specifications, figure 7.8.5-2). All are numeric; the
26
+ /// rest end with FNC1 unless last.
27
+ const PREDEFINED: [(&str, usize); 22] = [
28
+ ("00", 20),
29
+ ("01", 16),
30
+ ("02", 16),
31
+ ("03", 16),
32
+ ("04", 18),
33
+ ("11", 8),
34
+ ("12", 8),
35
+ ("13", 8),
36
+ ("14", 8),
37
+ ("15", 8),
38
+ ("16", 8),
39
+ ("17", 8),
40
+ ("18", 8),
41
+ ("19", 8),
42
+ ("20", 4),
43
+ ("31", 10),
44
+ ("32", 10),
45
+ ("33", 10),
46
+ ("34", 10),
47
+ ("35", 10),
48
+ ("36", 10),
49
+ ("41", 16),
50
+ ];
51
+
52
+ /// The longest value any AI takes.
53
+ const MAX_VALUE: usize = 90;
54
+
55
+ /// The GS1 check digit (modulo 10, weights 3 and 1 from the right) of
56
+ /// `digits`, as GTINs, SSCCs and GLNs end with; `None` unless all are
57
+ /// digits.
58
+ ///
59
+ /// ```
60
+ /// assert_eq!(imprimatur::barcode::check_digit("950600013435"), Some(2));
61
+ /// ```
62
+ #[must_use]
63
+ pub fn check_digit(digits: &str) -> Option<u8> {
64
+ let mut sum = 0_u32;
65
+ for (index, byte) in digits.bytes().rev().enumerate() {
66
+ byte.is_ascii_digit().then_some(())?;
67
+ let weight = if index % 2 == 0 { 3 } else { 1 };
68
+ sum += weight * u32::from(byte - b'0');
69
+ }
70
+ u8::try_from((10 - sum % 10) % 10).ok()
71
+ }
72
+
73
+ /// Reads an element string into its fields.
74
+ ///
75
+ /// # Errors
76
+ /// [`Error::InvalidElementString`] for anything but AIs of 2 to 4 digits in
77
+ /// parentheses, each with a value in GS1's character set, of the length
78
+ /// and check digit its AI takes.
79
+ pub fn parse(input: &str) -> Result<Vec<Field<'_>>> {
80
+ let invalid = |reason: String| Error::InvalidElementString(reason);
81
+ let bytes = input.as_bytes();
82
+ (!bytes.is_empty()).ok_or_else(|| invalid("it is empty".to_owned()))?;
83
+ let mut fields = Vec::new();
84
+ let mut at = 0;
85
+ while at < bytes.len() {
86
+ let length = ai_at(bytes, at).ok_or_else(|| {
87
+ invalid(format!("expected an AI such as (01) at character {}", at + 1))
88
+ })?;
89
+ let start = at + length + 2;
90
+ let end = (start..bytes.len()).find(|&next| ai_at(bytes, next).is_some());
91
+ let end = end.unwrap_or(bytes.len());
92
+ let field = Field { ai: &input[at + 1..at + 1 + length], value: &input[start..end] };
93
+ validate(field)?;
94
+ fields.push(field);
95
+ at = end;
96
+ }
97
+ Ok(fields)
98
+ }
99
+
100
+ /// `element_string`, parsed and run together as [`units`] does: what every
101
+ /// encoder needs before laying its symbol out.
102
+ ///
103
+ /// # Errors
104
+ /// [`Error::InvalidElementString`], as [`parse`].
105
+ pub fn element_units(element_string: &str) -> Result<Vec<Unit>> {
106
+ Ok(units(&parse(element_string)?))
107
+ }
108
+
109
+ /// The fields run together: no parentheses, FNC1 after each field of
110
+ /// variable length but the last.
111
+ pub fn units(fields: &[Field<'_>]) -> Vec<Unit> {
112
+ let mut units = Vec::new();
113
+ for (index, field) in fields.iter().enumerate() {
114
+ units.extend(field.ai.bytes().chain(field.value.bytes()).map(Unit::Char));
115
+ if predefined(field.ai).is_none() && index + 1 < fields.len() {
116
+ units.push(Unit::Fnc1);
117
+ }
118
+ }
119
+ units
120
+ }
121
+
122
+ /// The length of the AI in parentheses at `at`: 2 to 4 digits.
123
+ fn ai_at(bytes: &[u8], at: usize) -> Option<usize> {
124
+ (bytes.get(at) == Some(&b'(')).then_some(())?;
125
+ let digits = bytes[at + 1..].iter().take_while(|byte| byte.is_ascii_digit()).count();
126
+ ((2..=4).contains(&digits) && bytes.get(at + 1 + digits) == Some(&b')')).then_some(digits)
127
+ }
128
+
129
+ /// The length of the element string, AI included, when the AI fixes it.
130
+ fn predefined(ai: &str) -> Option<usize> {
131
+ PREDEFINED.iter().find(|(prefix, _)| ai.starts_with(prefix)).map(|&(_, length)| length)
132
+ }
133
+
134
+ fn validate(field: Field<'_>) -> Result<()> {
135
+ let Field { ai, value } = field;
136
+ let invalid = |reason: String| Err(Error::InvalidElementString(format!("({ai}) {reason}")));
137
+ if value.is_empty() {
138
+ return invalid("has no value".to_owned());
139
+ }
140
+ let outside = |character: char| !u8::try_from(character).is_ok_and(in_character_set);
141
+ if let Some(character) = value.chars().find(|&character| outside(character)) {
142
+ return invalid(format!("holds {character:?}, outside GS1's character set"));
143
+ }
144
+ if value.len() > MAX_VALUE {
145
+ return invalid(format!("holds {} characters, more than {MAX_VALUE}", value.len()));
146
+ }
147
+ let Some(length) = predefined(ai) else { return Ok(()) };
148
+ let digits = length - ai.len();
149
+ if value.len() != digits || !value.bytes().all(|byte| byte.is_ascii_digit()) {
150
+ return invalid(format!("takes {digits} digits, not {value:?}"));
151
+ }
152
+ // SSCC, GTINs and GLNs end with a check digit.
153
+ let checked = matches!(ai, "00" | "01" | "02") || ai.starts_with("41");
154
+ let (body, last) = value.split_at(value.len() - 1);
155
+ let expected = check_digit(body).unwrap_or(0);
156
+ if checked && last.as_bytes()[0] - b'0' != expected {
157
+ return invalid(format!("{value} has check digit {last}, not {expected}"));
158
+ }
159
+ Ok(())
160
+ }
161
+
162
+ /// GS1 AI encodable character set 82: letters, digits and
163
+ /// ``!"%&'()*+,-./:;<=>?_``.
164
+ const fn in_character_set(byte: u8) -> bool {
165
+ byte.is_ascii_alphanumeric()
166
+ || matches!(
167
+ byte,
168
+ b'!' | b'"'
169
+ | b'%'
170
+ | b'&'
171
+ | b'\''
172
+ | b'('
173
+ | b')'
174
+ | b'*'
175
+ | b'+'
176
+ | b','
177
+ | b'-'
178
+ | b'.'
179
+ | b'/'
180
+ | b':'
181
+ | b';'
182
+ | b'<'
183
+ | b'='
184
+ | b'>'
185
+ | b'?'
186
+ | b'_'
187
+ )
188
+ }
@@ -0,0 +1,130 @@
1
+ //! QR Code and Data Matrix, through the `qrcode` and `datamatrix` crates.
2
+
3
+ use datamatrix::{DataMatrix, SymbolList};
4
+ use qrcode::bits::Bits;
5
+ use qrcode::{Color, EcLevel, QrCode, QrResult, Version};
6
+
7
+ use super::Symbology;
8
+ use super::gs1::Unit;
9
+ use crate::Result;
10
+
11
+ /// How much of a QR Code may be damaged and still read: about 7%, 15%, 25%
12
+ /// or 30% of it.
13
+ #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
14
+ pub enum Level {
15
+ Low,
16
+ #[default]
17
+ Medium,
18
+ Quartile,
19
+ High,
20
+ }
21
+
22
+ impl From<Level> for EcLevel {
23
+ fn from(level: Level) -> Self {
24
+ match level {
25
+ Level::Low => Self::L,
26
+ Level::Medium => Self::M,
27
+ Level::Quartile => Self::Q,
28
+ Level::High => Self::H,
29
+ }
30
+ }
31
+ }
32
+
33
+ /// Where FNC1 separates fields, GS1 symbols carry `GS` (ASCII 29), which
34
+ /// is also what scanners transmit for it.
35
+ const GS: u8 = 0x1D;
36
+ /// Digits shorter than this stay in byte mode: switching to numeric mode
37
+ /// and back costs more than it saves.
38
+ const NUMERIC_RUN: usize = 6;
39
+
40
+ /// A QR Code's side in modules and its modules, row by row.
41
+ pub fn qr(data: &[u8], level: Level) -> Result<(usize, Vec<bool>)> {
42
+ let code = QrCode::with_error_correction_level(data, level.into())
43
+ .map_err(|_| too_long(Symbology::QrCode))?;
44
+ Ok(qr_modules(code))
45
+ }
46
+
47
+ /// A GS1 QR Code (FNC1 in first position) in the smallest version it fits.
48
+ pub fn gs1_qr(units: &[Unit], level: Level) -> Result<(usize, Vec<bool>)> {
49
+ let level = EcLevel::from(level);
50
+ let data = bytes(units);
51
+ let bits = (1..=40)
52
+ .find_map(|version| gs1_bits(&data, Version::Normal(version), level).ok())
53
+ .ok_or_else(|| too_long(Symbology::QrCode))?;
54
+ let code = QrCode::with_bits(bits, level).map_err(|_| too_long(Symbology::QrCode))?;
55
+ Ok(qr_modules(code))
56
+ }
57
+
58
+ /// Long runs of digits in numeric mode, the rest in byte mode: alphanumeric
59
+ /// mode would read a `%` in the data as a separator.
60
+ fn gs1_bits(data: &[u8], version: Version, level: EcLevel) -> QrResult<Bits> {
61
+ let mut bits = Bits::new(version);
62
+ bits.push_fnc1_first_position()?;
63
+ let mut rest = data;
64
+ while !rest.is_empty() {
65
+ let digits = rest.iter().take_while(|byte| byte.is_ascii_digit()).count();
66
+ if digits >= NUMERIC_RUN {
67
+ bits.push_numeric_data(&rest[..digits])?;
68
+ rest = &rest[digits..];
69
+ continue;
70
+ }
71
+ let mut end = 0;
72
+ while end < rest.len() {
73
+ let run = rest[end..].iter().take_while(|byte| byte.is_ascii_digit()).count();
74
+ if run >= NUMERIC_RUN {
75
+ break;
76
+ }
77
+ end += run.max(1);
78
+ }
79
+ bits.push_byte_data(&rest[..end])?;
80
+ rest = &rest[end..];
81
+ }
82
+ bits.push_terminator(level)?;
83
+ Ok(bits)
84
+ }
85
+
86
+ /// A square Data Matrix's columns and rows, and its modules row by row.
87
+ pub fn data_matrix(text: &str) -> Result<((usize, usize), Vec<bool>)> {
88
+ let code =
89
+ DataMatrix::encode_str(text, square()).map_err(|_| too_long(Symbology::DataMatrix))?;
90
+ Ok(modules(&code))
91
+ }
92
+
93
+ /// A square GS1 `DataMatrix`: FNC1 first, then the data.
94
+ pub fn gs1_data_matrix(units: &[Unit]) -> Result<((usize, usize), Vec<bool>)> {
95
+ let code = DataMatrix::encode_gs1(&bytes(units), square())
96
+ .map_err(|_| too_long(Symbology::DataMatrix))?;
97
+ Ok(modules(&code))
98
+ }
99
+
100
+ fn qr_modules(code: QrCode) -> (usize, Vec<bool>) {
101
+ (code.width(), code.into_colors().into_iter().map(|color| color == Color::Dark).collect())
102
+ }
103
+
104
+ fn square() -> SymbolList {
105
+ SymbolList::default().enforce_square()
106
+ }
107
+
108
+ fn modules(code: &DataMatrix) -> ((usize, usize), Vec<bool>) {
109
+ let bitmap = code.bitmap();
110
+ let (columns, rows) = (bitmap.width(), bitmap.height());
111
+ let mut modules = vec![false; columns * rows];
112
+ for (x, y) in bitmap.pixels() {
113
+ modules[y * columns + x] = true;
114
+ }
115
+ ((columns, rows), modules)
116
+ }
117
+
118
+ fn bytes(units: &[Unit]) -> Vec<u8> {
119
+ units
120
+ .iter()
121
+ .map(|unit| match unit {
122
+ Unit::Char(byte) => *byte,
123
+ Unit::Fnc1 => GS,
124
+ })
125
+ .collect()
126
+ }
127
+
128
+ fn too_long(symbology: Symbology) -> crate::Error {
129
+ symbology.invalid("too much data for the largest symbol")
130
+ }