diff --git a/Cargo.lock b/Cargo.lock index 0dcd26f..8250802 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,6 +417,22 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "awint_core" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2785d65c4c05d1c323506e8e8b64f855e99ecfb13e129655bc74a68b41cc6bca" +dependencies = [ + "awint_internals", + "const_fn", +] + +[[package]] +name = "awint_internals" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f66860693f788a4aa8917c99a37f9b6bb755243e6e7530777abed81a7698894" + [[package]] name = "bit-set" version = "0.9.1" @@ -656,6 +672,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const_fn" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413d67b29ef1021b4d60f4aa1e925ca031751e213832b4b1d588fae623c05c60" + [[package]] name = "core-foundation" version = "0.9.4" @@ -2315,6 +2337,13 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pdf417" +version = "0.2.1" +dependencies = [ + "awint_core", +] + [[package]] name = "peniko" version = "0.6.1" @@ -2493,6 +2522,8 @@ dependencies = [ "encoding_rs", "font8x8", "image", + "pdf417", + "qrcode", ] [[package]] @@ -2540,6 +2571,15 @@ version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" +[[package]] +name = "qrcode" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" +dependencies = [ + "image", +] + [[package]] name = "quick-error" version = "2.0.1" diff --git a/Cargo.toml b/Cargo.toml index 7557df7..ddeabfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,13 @@ eframe = "0.35.0" encoding_rs = "0.8.35" font8x8 = "0.3.1" image = { version = "0.25.10", default-features = false, features = ["png"] } +pdf417 = "0.2.1" +qrcode = "0.14.1" + +[patch.crates-io] +# pdf417 0.2.1 publica `#![feature(const_mut_refs)]` (estable desde 1.83); +# se vende localmente sin esa línea para poder compilar en stable. +pdf417 = { path = "vendor/pdf417" } [profile.release] opt-level = "z" # optimizar para tamaño diff --git a/README.md b/README.md index f075056..a42f5eb 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,27 @@ Emulador de impresora POS (ESC/POS) para Windows, escrito en Rust. Crea una impr ## ESC/POS soportado -- Texto, alineación (izquierda/centro/derecha), negrita, subrayado, tamaño de fuente doble (ancho/alto), fuente A/B. +- Texto, alineación (izquierda/centro/derecha), negrita, subrayado (simple/doble), inverso (`GS b`, `GS B`, bit 6 de `ESC !`), cursiva (`ESC 4`/`ESC 5`), tamaño doble (ancho/alto), fuente A/B (`ESC !`, `ESC %`), espaciado entre caracteres (`ESC SP`, `GS SP`). +- **UTF-8 multibyte**: las secuencias UTF-8 válidas (á, ñ, €, emoji...) se decodifican automáticamente; el resto de bytes altos se decodifican por codepage. +- **Envoltorio automático**: las líneas que exceden el ancho de papel/área de impresión saltan de línea como una impresora real. - Codepages: CP437, CP850, CP858, CP866, CP1252 (`ESC t`). - Avance de línea/puntos (`LF`, `FF`, `ESC d`, `ESC J`), espaciado de línea (`ESC 3`, `ESC A`). -- Cortes de papel completo/parcial (`ESC i`, `ESC v`), apertura de cajón (`ESC p`). -- Códigos de barras (`GS k`: CODE39, EAN-13, EAN-8, etc.) con HRI. +- Posicionamiento: posición absoluta (`ESC $`, `GS $`) y relativa (`ESC \`, `GS \`), márgenes izquierdo (`ESC l`, `ESC L`, `GS L`) y área de impresión (`ESC W`, `GS W`). +- Cortes de papel completo/parcial (`ESC i`, `ESC v`, `GS V`), apertura de cajón (`ESC p`). +- Códigos de barras 1D (`GS k`: CODE39, EAN-13, EAN-8, **Code 128**, **ITF**, UPC-A...) con HRI; EAN/UPC/Code 128/ITF/Code 39 se dibujan con módulos reales (barras y espacios reales, dígito de control calculado/verificado). +- Códigos 2D: **QR** (`GS ( k` cn=49, incl. modelo/tamaño/ECC y store+print) y **PDF417** (`GS ( k` cn=48). - Imágenes raster (`GS v 0`) y bitmap (`ESC *`). +- Logos NV: definir (`FS q`, `GS ( L`, `GS 8 L`) e imprimir con escalado (`FS p`, `GS /`, `GS 8 H`). +- **Transmit de códigos 2D**: `GS ( k` fn 81 (QR y PDF417) devuelve los datos almacenados por el socket, como una impresora real. +- **Modo página**: `GS P` (área y entrada), `GS T` (dirección), texto con posicionamiento absoluto y descarga con `ESC FF`. +- **Fuente fija tipo térmica**: cada carácter ocupa una celda de ancho constante (12×24 fuente A, 9×17 fuente B), como una térmica real. +- **Estado simulado configurable en la GUI**: toggles de papel agotado, casi-fin, tapa abierta, error y cajón abierto; los bits de `DLE EOT`, `DLE ENQ`, `GS r` y ASB cambian en consecuencia. +- **Zumbador**: `ESC ( C` fn 0x06 (aviso sonoro, registrado en el resumen). +- **Fuentes descargadas**: `ESC &` y `GS ( A` (almacenadas y resumidas). +- Respuestas de estado en tiempo real: `DLE EOT 1..8/10..19`, `DLE ENQ`, `GS r` (estado de impresora/offline/error/papel/recovery/búfer, escritas al socket como en una impresora real). +- **Estado automático (ASB)**: `GS a n` envía los 4 bytes de estado al activarse **y empuja automáticamente los cambios de estado posteriores** (un hilo vigila la máquina de estado mientras la conexión esté activa). +- **Mecánica física**: el avance usa unidades de movimiento vertical reales (1/203", ~0.125 mm); botones **Feed** y **Corte** en la GUI que envían `ESC J n` y `GS V 66` por el mismo pipeline que una aplicación real. +- **Memoria NV persistente**: logos, fuentes descargadas y densidad se guardan en un fichero binario (`%LOCALAPPDATA%\pos_printer_emulator\nv.bin`) y se restauran entre sesiones. ## Requisitos @@ -32,6 +47,16 @@ cargo run --release El binario se genera en `target\release\pos_printer_emulator.exe` y no muestra ventana de consola. +> Nota: la dependencia `pdf417` 0.2.1 se vende en `vendor/pdf417` (el crate publicado incluye `#![feature(const_mut_refs)]`, estable desde Rust 1.83, que rompe el compilador stable; se patchea localmente vía `[patch.crates-io]`). + +## Pruebas + +```powershell +cargo test +``` + +Suite de 28 pruebas: parser (estilos, posicionamiento y márgenes, corte, QR/PDF417, UTF-8, ASB, raster `GS 8 L/H`, escalas `GS /`, modo página, transmit fn 81, zumbador y fuentes descargadas), estado simulado (`state.rs`), módulos de barcode reales (`barcode.rs`: EAN/Code 128/ITF/Code 39), persistencia NV (`nvstore.rs`) y pruebas de integración por socket real (`server.rs`) que verifican `DLE EOT` completo, ASB push y el transmit de QR. + ## Uso 1. Ejecuta la aplicación. El servidor TCP 9100 arranca automáticamente. @@ -69,15 +94,19 @@ Para dejar de usar la impresora, pulsa **Quitar impresora**. | Módulo | Responsabilidad | | ----------------- | --------------------------------------------------------- | -| `server.rs` | Listener TCP 9100, un hilo por conexión, límites de jobs. | +| `server.rs` | Listener TCP 9100, un hilo por conexión, límites de jobs, respuestas real-time (`DLE EOT 1..19`, `DLE ENQ`, `GS r`, ASB push) y transmit fn 81. | | `parser.rs` | Convierte bytes ESC/POS en una lista de ítems renderizables y genera el resumen. | | `render.rs` | Dibuja el ticket en un bitmap RGBA (fuentes del sistema + fallback de 8×8). | +| `barcode.rs` | Dígitos de control y patrones de módulos reales (EAN/UPC, Code 128, ITF, Code 39) + validación de `GS k`. | +| `barcode2d.rs` | Genera QR (`qrcode`) y PDF417 (`pdf417`) como bitmap. | | `codepages.rs` | Tablas de conversión CP437/850/858/866/1252. | | `printer.rs` | Scripts PowerShell para registrar/quitar la impresora (con UAC). | -| `sample.rs` | Documento de prueba con texto, barcode y raster. | +| `nvstore.rs` | Persistencia NV entre sesiones (fichero binario). | +| `sample.rs` | Documento de prueba con texto, QR/PDF417 (+transmit), barcode (EAN-13, Code 128, ITF), logos NV (`FS q`, `GS 8 L`, escalas `GS /`), modo página, buzzer y fuente descargada. | | `main.rs` | Interfaz egui/eframe. | ## Limitaciones - Solo funciona en Windows para el registro de la impresora (el servidor TCP es multiplataforma). +- Las fuentes descargadas (`ESC &`, `GS ( A`) se reconocen y almacenan, pero no se renderizan aún; el modo página soporta la dirección 0 (izquierda→derecha, arriba→abajo). - El puerto RAW simula el envío de la aplicación a la impresora; no es un driver de impresora USB virtual (eso requeriría un driver de kernel firmado). El resultado es equivalente para la mayoría de software de punto de venta. diff --git a/src/barcode.rs b/src/barcode.rs new file mode 100644 index 0000000..416bacf --- /dev/null +++ b/src/barcode.rs @@ -0,0 +1,479 @@ +// Códigos de barras 1D: dígitos de control y patrones de módulos reales (EAN/UPC). + +const L_CODES: [[bool; 7]; 10] = [ + [false, false, false, true, true, false, true], + [false, false, true, true, false, false, true], + [false, false, true, false, false, true, true], + [false, true, true, true, true, false, true], + [false, true, false, false, false, true, true], + [false, true, true, false, false, false, true], + [false, true, false, true, true, true, true], + [false, true, true, true, false, true, true], + [false, true, true, false, true, true, true], + [false, false, false, true, false, true, true], +]; + +// Patrones de paridad del primer dígito EAN-13: 0=L, 1=G +const PARITY: [[u8; 6]; 10] = [ + [0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 1, 1], + [0, 0, 1, 1, 0, 1], + [0, 0, 1, 1, 1, 0], + [0, 1, 0, 0, 1, 1], + [0, 1, 1, 0, 0, 1], + [0, 1, 1, 1, 0, 0], + [0, 1, 0, 1, 0, 1], + [0, 1, 0, 1, 1, 0], + [0, 1, 1, 0, 1, 0], +]; + +fn l_code(d: usize) -> &'static [bool; 7] { + &L_CODES[d] +} + +fn g_code(d: usize) -> [bool; 7] { + let mut g = [false; 7]; + for i in 0..7 { + g[i] = L_CODES[d][6 - i]; + } + g +} + +fn c_code(d: usize) -> [bool; 7] { + let l = L_CODES[d]; + let mut c = [false; 7]; + for i in 0..7 { + c[i] = l[6 - i]; + } + c +} + +// Dígito de control EAN-13 / EAN-8 / UPC-A (pesos 3 y 1 desde la derecha). +pub fn check_digit(digits: &[u8]) -> u8 { + let mut sum: u32 = 0; + let n = digits.len(); + for (i, &d) in digits.iter().enumerate() { + let weight = if (n - 1 - i) % 2 == 0 { 3 } else { 1 }; + sum += ((d - b'0') as u32) * weight; + } + ((10 - (sum % 10)) % 10) as u8 +} + +fn digits_from(data: &[u8]) -> Option> { + data.iter() + .map(|&b| { + if (b'0'..=b'9').contains(&b) { + Some(b - b'0') + } else { + None + } + }) + .collect() +} + +// EAN-13: recibe 13 dígitos (con dígito de control) y produce 95 módulos. +pub fn ean13_modules(digits: &[u8]) -> Option> { + let d = digits_from(digits)?; + if d.len() != 13 { + return None; + } + let parity = PARITY[d[0] as usize]; + let mut out = Vec::with_capacity(95); + out.extend_from_slice(&[true, false, true]); // guarda inicio + for (i, &dig) in d[1..7].iter().enumerate() { + if parity[i] == 0 { + out.extend_from_slice(l_code(dig as usize)); + } else { + out.extend_from_slice(&g_code(dig as usize)); + } + } + out.extend_from_slice(&[false, true, false, true, false]); // separador central + for &dig in &d[7..13] { + out.extend_from_slice(&c_code(dig as usize)); + } + out.extend_from_slice(&[true, false, true]); // guarda final + Some(out) +} + +// EAN-8: recibe 8 dígitos (con dígito de control) y produce 67 módulos. +pub fn ean8_modules(digits: &[u8]) -> Option> { + let d = digits_from(digits)?; + if d.len() != 8 { + return None; + } + let mut out = Vec::with_capacity(67); + out.extend_from_slice(&[true, false, true]); + for &dig in &d[0..4] { + out.extend_from_slice(l_code(dig as usize)); + } + out.extend_from_slice(&[false, true, false, true, false]); + for &dig in &d[4..8] { + out.extend_from_slice(&c_code(dig as usize)); + } + out.extend_from_slice(&[true, false, true]); + Some(out) +} + +// UPC-A (12 dígitos) = EAN-13 con primer dígito 0. +pub fn upca_as_ean13(digits: &[u8]) -> Option> { + let d = digits_from(digits)?; + if d.len() != 12 { + return None; + } + let mut ean = Vec::with_capacity(13); + ean.push(0); + ean.extend_from_slice(&d); + Some(ean) +} + +// Normaliza y valida datos de GS k; devuelve (datos, aviso opcional). +pub fn validate(m: u8, data: &[u8]) -> (Vec, Option) { + // Los códigos 1D admiten dos familias de m: 0..=6 (longitud explícita) y + // 65..=73 (terminado en NUL). Se unifican para validar igual. + let norm_m = match m { + 0 | 65 => 65, // UPC-A + 2 | 67 => 67, // EAN-13 + 3 | 68 => 68, // EAN-8 + 4 | 69 => 69, // Code 39 + 5 | 70 => 70, // ITF + 6 | 71 => 71, // Codabar + other => other, + }; + let digits = |d: &[u8]| d.iter().all(|&b| (b'0'..=b'9').contains(&b)); + match norm_m { + 65 => { + // UPC-A: 11 o 12 dígitos (se añade el dígito de control si falta) + if digits(data) && (data.len() == 11 || data.len() == 12) { + let mut d = data.to_vec(); + if d.len() == 11 { + let cd = check_digit(&d); + d.push(b'0' + cd); + (d, Some("UPC-A: dígito de control calculado".to_string())) + } else { + let cd = check_digit(&d[..11]); + let msg = if d[11] == b'0' + cd { + None + } else { + Some(format!( + "UPC-A: dígito de control inválido (era {}, debería ser {cd})", + d[11] as char + )) + }; + (d, msg) + } + } else { + (data.to_vec(), Some("UPC-A: se requieren 11/12 dígitos".to_string())) + } + } + 67 => { + if digits(data) && (data.len() == 12 || data.len() == 13) { + let mut d = data.to_vec(); + if d.len() == 12 { + let cd = check_digit(&d); + d.push(b'0' + cd); + (d, Some("EAN-13: dígito de control calculado".to_string())) + } else { + let cd = check_digit(&d[..12]); + let msg = if d[12] == b'0' + cd { + None + } else { + Some(format!( + "EAN-13: dígito de control inválido (era {}, debería ser {cd})", + d[12] as char + )) + }; + (d, msg) + } + } else { + (data.to_vec(), Some("EAN-13: se requieren 12/13 dígitos".to_string())) + } + } + 68 => { + if digits(data) && (data.len() == 7 || data.len() == 8) { + let mut d = data.to_vec(); + if d.len() == 7 { + let cd = check_digit(&d); + d.push(b'0' + cd); + (d, Some("EAN-8: dígito de control calculado".to_string())) + } else { + let cd = check_digit(&d[..7]); + let msg = if d[7] == b'0' + cd { + None + } else { + Some(format!( + "EAN-8: dígito de control inválido (era {}, debería ser {cd})", + d[7] as char + )) + }; + (d, msg) + } + } else { + (data.to_vec(), Some("EAN-8: se requieren 7/8 dígitos".to_string())) + } + } + 69 => { + // Code 39: A-Z, 0-9 y - . espacio $ / + % * + let ok = data.iter().all(|&b| { + (b'A'..=b'Z').contains(&b) + || (b'0'..=b'9').contains(&b) + || matches!(b, b'-' | b'.' | b' ' | b'$' | b'/' | b'+' | b'%' | b'*') + }); + if ok { + (data.to_vec(), None) + } else { + (data.to_vec(), Some("Code 39: carácter inválido".to_string())) + } + } + 70 => { + // ITF: número par de dígitos + if digits(data) && data.len() % 2 == 0 { + (data.to_vec(), None) + } else { + (data.to_vec(), Some("ITF: se requiere número par de dígitos".to_string())) + } + } + 71 => { + // Codabar: 0-9 y - $ : / . + + let ok = data.iter().all(|&b| { + (b'0'..=b'9').contains(&b) || matches!(b, b'-' | b'$' | b':' | b'/' | b'.' | b'+') + }); + if ok { + (data.to_vec(), None) + } else { + (data.to_vec(), Some("Codabar: carácter inválido".to_string())) + } + } + 72 => { + // Code 93: mismo juego de caracteres que Code 39 + let ok = data.iter().all(|&b| { + (b'A'..=b'Z').contains(&b) + || (b'0'..=b'9').contains(&b) + || matches!(b, b'-' | b'.' | b' ' | b'$' | b'/' | b'+' | b'%') + }); + if ok { + (data.to_vec(), None) + } else { + (data.to_vec(), Some("Code 93: carácter inválido".to_string())) + } + } + 73 => { + // Code 128: bytes 0x00..=0x7F + if data.iter().all(|&b| b <= 0x7F) { + (data.to_vec(), None) + } else { + (data.to_vec(), Some("Code 128: byte > 0x7F no soportado".to_string())) + } + } + _ => (data.to_vec(), None), + } +} + +// Tabla de patrones del Code 128 (valores 0..=105, 11 módulos; parada 13). +const CODE128: [&str; 106] = [ + "11011001100", "11001101100", "11001100110", "10010011000", "10010001100", + "10001001100", "10011001000", "10011000100", "10001100100", "11001001000", + "11001000100", "11000100100", "10110011100", "10011011100", "10011001110", + "10111001100", "10011101100", "10011100110", "11001110010", "11001011100", + "11001001110", "11011100100", "11001110100", "11101101110", "11101001100", + "11100101100", "11100100110", "11101100100", "11100110100", "11100110010", + "11011011000", "11011000110", "11000110110", "10100011000", "10001011000", + "10001000110", "10110001000", "10001101000", "10001100010", "11010001000", + "11000101000", "11000100010", "10110111000", "10110001110", "10001101110", + "10111011000", "10111000110", "10001110110", "11101110110", "11010001110", + "11000101110", "11011101000", "11011100010", "11011101110", "11101011000", + "11101000110", "11100010110", "11101101000", "11101100010", "11100011010", + "11101111010", "11001000010", "11110001010", "10100110000", "10100001100", + "10010110000", "10010000110", "10000101100", "10000100110", "10110010000", + "10110000100", "10011010000", "10011000010", "10000110100", "10000110010", + "11000010010", "11001010000", "11110111010", "11000010100", "10001111010", + "10100111100", "10010111100", "10010011110", "10111100100", "10011110100", + "10011110010", "11110100100", "11110010100", "11110010010", "11011011110", + "11011110110", "11110110110", "10101111000", "10100011110", "10001011110", + "10111101000", "10111100010", "11110101000", "11110100010", "10111011110", + "10111101110", "11101011110", "11110101110", "11010000100", "11010010000", + "11010011100", +]; + +// Code 128: selecciona el subjuego más compacto (C para dígitos pares, A/B +// para texto), calcula la suma de control y devuelve los módulos. +pub fn code128_modules(data: &[u8]) -> Option> { + if data.is_empty() { + return None; + } + let code_c = data.len() % 2 == 0 && data.iter().all(|b| b.is_ascii_digit()); + let mut values: Vec = Vec::new(); + if code_c { + values.push(105); // Start C + let mut i = 0; + while i < data.len() { + let v = (data[i] - b'0') as usize * 10 + (data[i + 1] - b'0') as usize; + values.push(v); + i += 2; + } + } else { + let use_a = data.iter().all(|&b| b < 0x20 || (0x20..=0x5F).contains(&b)); + values.push(if use_a { 103 } else { 104 }); // Start A / Start B + for &b in data { + let v = if use_a { + if b < 0x20 { + b as usize + 64 + } else { + b as usize - 32 + } + } else { + if b < 0x20 { + return None; + } + b as usize - 32 + }; + values.push(v); + } + } + let mut sum = values[0]; + for (i, &v) in values.iter().enumerate().skip(1) { + sum = (sum + v * i) % 103; + } + values.push(sum); + let mut out = Vec::with_capacity(values.len() * 11 + 13); + for v in values { + for c in CODE128[v].bytes() { + out.push(c == b'1'); + } + } + for c in "1100011101011".bytes() { + out.push(c == b'1'); + } + Some(out) +} + +// Tabla de anchos de ITF (1 = ancho, 0 = estrecho), 5 elementos por dígito. +const ITF_DIGITS: [[u8; 5]; 10] = [ + [0, 0, 1, 1, 0], + [1, 0, 0, 0, 1], + [0, 1, 0, 0, 1], + [1, 1, 0, 0, 0], + [0, 0, 1, 0, 1], + [1, 0, 1, 0, 0], + [0, 1, 1, 0, 0], + [0, 0, 0, 1, 1], + [1, 0, 0, 1, 0], + [0, 1, 0, 1, 0], +]; + +// ITF (Interleaved 2 of 5): los dígitos se agrupan en pares; el primero +// codifica barras y el segundo espacios. +pub fn itf_modules(data: &[u8]) -> Option> { + let d = digits_from(data)?; + if d.len() < 2 || d.len() % 2 != 0 { + return None; + } + let mut out = Vec::new(); + out.extend_from_slice(&[true, false, true, false]); // start + for pair in d.chunks(2) { + let bars = ITF_DIGITS[pair[0] as usize]; + let spaces = ITF_DIGITS[pair[1] as usize]; + for i in 0..5 { + let bw = if bars[i] == 1 { 3 } else { 1 }; + for _ in 0..bw { + out.push(true); + } + let sw = if spaces[i] == 1 { 3 } else { 1 }; + for _ in 0..sw { + out.push(false); + } + } + } + // stop: barra ancha, espacio estrecho, barra estrecha + out.extend_from_slice(&[true, true, true, false, true]); + Some(out) +} + +// Tabla de patrones del Code 39 (9 elementos, 1 = ancho, 0 = estrecho). +const CODE39: [&str; 44] = [ + "000110100", "100100001", "001100001", "101100000", "000110001", "100110000", + "001110000", "000100101", "100100100", "001100100", "100001001", "001001001", + "101001000", "000011001", "100011000", "001011000", "000001101", "100001100", + "001001100", "000011100", "100000011", "001000011", "101000010", "000010011", + "100010010", "001010010", "000000111", "100000110", "001000110", "000010110", + "110000001", "011000001", "111000000", "010010001", "110010000", "011010000", + "010000101", "110000100", "011000100", "010101000", "010100010", "010001010", + "000101010", "010010100", +]; + +fn code39_index(c: u8) -> Option { + match c { + b'0'..=b'9' => Some((c - b'0') as usize), + b'A'..=b'Z' => Some((c - b'A') as usize + 10), + b'-' => Some(36), + b'.' => Some(37), + b' ' => Some(38), + b'$' => Some(39), + b'/' => Some(40), + b'+' => Some(41), + b'%' => Some(42), + b'*' => Some(43), + _ => None, + } +} + +// Code 39: símbolo completo con asteriscos de inicio/parada. +pub fn code39_modules(data: &[u8]) -> Option> { + let mut out = Vec::new(); + for c in std::iter::once(&b'*').chain(data.iter()).chain(std::iter::once(&b'*')) { + let idx = code39_index(*c)?; + for ch in CODE39[idx].bytes() { + // cada elemento: barra/espacio alterno de 1 o 3 módulos + let wide = ch == b'1'; + let w = if wide { 3 } else { 1 }; + for _ in 0..w { + out.push(true); + } + out.push(false); + } + } + out.pop(); // quitar el espacio final tras la última barra + Some(out) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ean13_control_y_modulos() { + assert_eq!(check_digit(b"400638133393"), 1); + let m = ean13_modules(b"4006381333931").unwrap(); + assert_eq!(m.len(), 95); + } + + #[test] + fn upc_a_con_control_automatico() { + let (d, warn) = validate(65, b"01234567890"); + assert_eq!(d, b"012345678905"); + assert!(warn.is_some()); + } + + #[test] + fn code128_digitos_y_texto() { + let d = code128_modules(b"123456").unwrap(); + assert_eq!(d.len(), 11 + 3 * 11 + 11 + 13); // start C + 3 pares + checksum + stop + let t = code128_modules(b"ABC").unwrap(); + assert!(!t.is_empty()); + assert_eq!(code128_modules(b""), None); + } + + #[test] + fn itf_pares_y_modulos() { + assert!(itf_modules(b"1234").is_some()); + assert_eq!(itf_modules(b"123"), None); + assert_eq!(itf_modules(b"12A4"), None); + } + + #[test] + fn code39_modulos() { + let m = code39_modules(b"ABC-123").unwrap(); + assert!(!m.is_empty()); + assert_eq!(code39_modules(b"a"), None); + assert_eq!(code39_index(b'Z'), Some(35)); + } +} diff --git a/src/barcode2d.rs b/src/barcode2d.rs new file mode 100644 index 0000000..3815183 --- /dev/null +++ b/src/barcode2d.rs @@ -0,0 +1,89 @@ +use qrcode::{Color, EcLevel, QrCode}; + +/// Bitmap (sin zona de silencio) de un código 2D generado. +pub struct Bitmap2D { + pub width: usize, + pub height: usize, + /// Fila principal (row-major), `true` = módulo oscuro. + pub dark: Vec, +} + +const ECC_COUNT: [usize; 9] = [2, 4, 8, 16, 32, 64, 128, 256, 512]; +const PDF417_MAX_CODEWORDS: usize = 30 * 90; + +fn rows_for(count: usize, cols: u8, level: u8) -> usize { + let n = count + ECC_COUNT[level as usize]; + (n + cols as usize - 1) / cols as usize +} + +/// Genera un QR. `ecc`: 0/48=L, 1/49=M, 2/50=Q, 3/51=H (cualquier otro valor = M). +pub fn qr(data: &[u8], ecc: u8) -> Option { + if data.is_empty() { + return None; + } + let level = match ecc { + 0 | 48 => EcLevel::L, + 2 | 50 => EcLevel::Q, + 3 | 51 => EcLevel::H, + _ => EcLevel::M, + }; + let code = QrCode::with_error_correction_level(data, level).ok()?; + let w = code.width(); + let dark: Vec = code.to_colors().iter().map(|&c| c == Color::Dark).collect(); + Some(Bitmap2D { + width: w, + height: w, + dark, + }) +} + +/// Genera un PDF417. `cols`/`rows`/`ecc` opcionales (se autodimensiona si faltan). +pub fn pdf417( + data: &[u8], + cols: Option, + rows: Option, + ecc: Option, +) -> Option { + if data.is_empty() { + return None; + } + let mut scratch = vec![0u16; PDF417_MAX_CODEWORDS]; + let count = pdf417::PDF417Encoder::new(&mut scratch, false) + .append_bytes(data) + .count(); + if count == 0 || count > PDF417_MAX_CODEWORDS { + return None; + } + let cols = cols.unwrap_or(30).clamp(1, 30); + let level = if let Some(l) = ecc { + l.clamp(0, 8) + } else { + let mut l = 8u8; + while l > 0 && rows_for(count, cols, l) > 90 { + l -= 1; + } + l + }; + let rows = if let Some(r) = rows { + r.clamp(3, 90) + } else { + rows_for(count, cols, level).clamp(3, 90) as u8 + }; + let capacity = rows as usize * cols as usize; + if capacity < count + ECC_COUNT[level as usize] { + return None; + } + let mut codewords = vec![0u16; capacity]; + let enc = pdf417::PDF417Encoder::new(&mut codewords, false).append_bytes(data); + enc.seal(level); + let width = 17 * cols as usize + 69; + let height = rows as usize; + let mut dark = vec![false; width * height]; + let pdf = pdf417::PDF417::new(&codewords, rows, cols, level); + pdf.render(dark.as_mut_slice()); + Some(Bitmap2D { + width, + height, + dark, + }) +} diff --git a/src/main.rs b/src/main.rs index ec4b722..7bdd986 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ #![cfg_attr(windows, windows_subsystem = "windows")] +mod barcode; +mod barcode2d; mod codepages; +mod nvstore; mod parser; mod printer; mod render; @@ -44,6 +47,10 @@ impl EmuApp { let stop = Arc::new(AtomicBool::new(false)); let port: u16 = 9100; let ctx = cc.egui_ctx.clone(); + // Cargar la memoria NV persistente (logos, fuentes, densidad) + if let Some(m) = nvstore::load() { + state.lock().unwrap().memory = m; + } let server = Some(server::spawn( state.clone(), fonts.clone(), @@ -95,17 +102,37 @@ impl EmuApp { fn send_test(&mut self) { let data = sample::demo_receipt(); + self.send_bytes( + data, + format!("Documento de prueba enviado a 127.0.0.1:{}", self.port), + ); + } + + // Avance físico de papel: ESC J n avanza n/203 pulgada (~0.125 mm por unidad). + fn send_feed(&mut self, units: usize) { + let mut d = Vec::new(); + d.extend_from_slice(&[0x1B, 0x4A]); + d.push(units.min(255) as u8); + let mm = units as f32 * (25.4 / 203.0); + self.send_bytes( + d, + format!("Avance físico: {units} unidades ≈ {mm:.1} mm (203 dpi)"), + ); + } + + fn send_cut(&mut self) { + self.send_bytes(vec![0x1D, 0x56, 0x42], "Corte de papel (GS V 66)".to_string()); + } + + fn send_bytes(&mut self, data: Vec, note: String) { match std::net::TcpStream::connect(("127.0.0.1", self.port)) { Ok(mut s) => { use std::io::Write; let _ = s.write_all(&data); let _ = s.shutdown(std::net::Shutdown::Both); - self.msg = Some(format!( - "Documento de prueba enviado a 127.0.0.1:{}", - self.port - )); + self.msg = Some(note); } - Err(e) => self.msg = Some(format!("No se pudo enviar el documento de prueba: {e}")), + Err(e) => self.msg = Some(format!("No se pudo enviar el documento: {e}")), } } @@ -281,6 +308,16 @@ impl EmuApp { } } ui.separator(); + ui.label("Estado simulado:"); + { + let mut s = self.state.lock().unwrap(); + ui.checkbox(&mut s.sim.paper_out, "Papel agotado"); + ui.checkbox(&mut s.sim.near_end, "Casi fin"); + ui.checkbox(&mut s.sim.cover_open, "Tapa abierta"); + ui.checkbox(&mut s.sim.error, "Error"); + ui.checkbox(&mut s.sim.drawer_open, "Cajón abierto"); + } + ui.separator(); if ui .add( egui::Button::new( @@ -293,6 +330,15 @@ impl EmuApp { { self.send_test(); } + if running { + ui.separator(); + if ui.button("Feed").on_hover_text("Avance físico de papel (ESC J, ~12 mm)").clicked() { + self.send_feed(100); + } + if ui.button("Corte").on_hover_text("Corte de papel (GS V 66)").clicked() { + self.send_cut(); + } + } ui.separator(); if ui.button("Registrar impresora en Windows").clicked() { let r = printer::register_printer(); diff --git a/src/nvstore.rs b/src/nvstore.rs new file mode 100644 index 0000000..c73159d --- /dev/null +++ b/src/nvstore.rs @@ -0,0 +1,147 @@ +// Persistencia NV real: guarda logos, fuentes descargadas y densidad en un +// fichero binario para que sobrevivan entre sesiones (como la flash de la +// impresora). Formato propio, sin dependencias. +// +// "POSNV1" (6) +// logo: 1 byte presente, u32le width, u32le height, u32le len, bytes +// fonts: u32le count, por fuente: 1 byte carácter, u32le len, bytes +// density: 1 byte +use std::io; +use std::path::PathBuf; + +use crate::parser::LogoItem; +use crate::state::PrinterMemory; + +const MAGIC: &[u8; 6] = b"POSNV1"; + +pub fn nv_path() -> PathBuf { + if let Some(p) = std::env::var_os("POS_NV_PATH") { + return PathBuf::from(p); + } + let base = std::env::var_os("LOCALAPPDATA") + .map(PathBuf::from) + .unwrap_or_else(|| std::env::temp_dir().join("pos_printer_emulator")); + base.join("pos_printer_emulator") + .join("nv.bin") +} + +pub fn save(memory: &PrinterMemory) -> io::Result<()> { + save_at(memory, &nv_path()) +} + +pub fn save_at(memory: &PrinterMemory, path: &PathBuf) -> io::Result<()> { + let mut out = Vec::with_capacity(128 + memory.logo.as_ref().map_or(0, |l| l.data.len())); + out.extend_from_slice(MAGIC); + match &memory.logo { + Some(l) => { + out.push(1); + out.extend_from_slice(&(l.width as u32).to_le_bytes()); + out.extend_from_slice(&(l.height as u32).to_le_bytes()); + out.extend_from_slice(&(l.data.len() as u32).to_le_bytes()); + out.extend_from_slice(&l.data); + } + None => out.push(0), + } + out.extend_from_slice(&(memory.user_font.len() as u32).to_le_bytes()); + for (ch, bytes) in &memory.user_font { + out.push(*ch); + out.extend_from_slice(&(bytes.len() as u32).to_le_bytes()); + out.extend_from_slice(bytes); + } + out.push(memory.density); + + if let Some(dir) = path.parent() { + let _ = std::fs::create_dir_all(dir); + } + std::fs::write(path, out) +} + +pub fn load() -> Option { + load_at(&nv_path()) +} + +pub fn load_at(path: &PathBuf) -> Option { + let bytes = std::fs::read(path).ok()?; + if bytes.len() < 7 || &bytes[..6] != MAGIC { + return None; + } + let mut tail = bytes; + tail.drain(..6); + let mut it = tail.into_iter(); + let u32_le = |it: &mut std::vec::IntoIter| -> Option { + let mut b = [0u8; 4]; + for slot in &mut b { + *slot = it.next()?; + } + Some(u32::from_le_bytes(b)) + }; + let logo = match it.next()? { + 0 => None, + _ => { + let width = u32_le(&mut it)? as usize; + let height = u32_le(&mut it)? as usize; + let len = u32_le(&mut it)? as usize; + let mut data = vec![0u8; len]; + for b in &mut data { + *b = it.next()?; + } + Some(LogoItem { data, width, height }) + } + }; + let nf = u32_le(&mut it)? as usize; + let mut user_font = Vec::with_capacity(nf); + for _ in 0..nf { + let ch = it.next()?; + let len = u32_le(&mut it)? as usize; + let mut data = vec![0u8; len]; + for b in &mut data { + *b = it.next()?; + } + user_font.push((ch, data)); + } + let density = it.next()?; + Some(PrinterMemory { + logo, + user_font, + density, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn roundtrip_con_logo_fuente_y_densidad() { + let dir = std::env::temp_dir().join(format!("pos_nv_test_r_{}", std::process::id())); + let path = dir.join("nv.bin"); + let _ = std::fs::remove_file(&path); + let mem = PrinterMemory { + logo: Some(LogoItem { + data: vec![0xAA, 0x55, 0xFF], + width: 16, + height: 8, + }), + user_font: vec![(b'A', vec![1, 2, 3, 4, 5, 6, 7, 8])], + density: 42, + }; + assert!(save_at(&mem, &path).is_ok()); + let loaded = load_at(&path).unwrap(); + assert_eq!(loaded.logo.unwrap().data, vec![0xAA, 0x55, 0xFF]); + assert_eq!(loaded.user_font, vec![(b'A', vec![1, 2, 3, 4, 5, 6, 7, 8])]); + assert_eq!(loaded.density, 42); + let _ = std::fs::remove_file(&path); + } + + #[test] + fn vacio_y_corrupto() { + let dir = std::env::temp_dir().join(format!("pos_nv_test_v_{}", std::process::id())); + let path = dir.join("nv.bin"); + let _ = std::fs::remove_file(&path); + assert!(save_at(&PrinterMemory::default(), &path).is_ok()); + assert!(load_at(&path).unwrap().logo.is_none()); + std::fs::write(&path, b"NO-MAGIC").unwrap(); + assert!(load_at(&path).is_none()); + let _ = std::fs::remove_file(&path); + } +} diff --git a/src/parser.rs b/src/parser.rs index 216be0f..1d89b6d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,4 +1,5 @@ use crate::codepages::{self, Codepage}; +use crate::state::PrinterMemory; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Alignment { @@ -19,6 +20,10 @@ pub struct Style { pub bold: bool, pub double_strike: bool, pub underline: bool, + pub underline2: bool, + pub reverse: bool, + pub italic: bool, + pub char_spacing: u8, pub size_x: u8, pub size_y: u8, } @@ -30,6 +35,10 @@ impl Default for Style { bold: false, double_strike: false, underline: false, + underline2: false, + reverse: false, + italic: false, + char_spacing: 0, size_x: 1, size_y: 1, } @@ -75,6 +84,35 @@ pub struct BarcodeItem { pub module: u32, } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Barcode2DKind { + Qr, + Pdf417, +} + +#[derive(Clone, Debug)] +pub struct Barcode2DItem { + pub kind: Barcode2DKind, + pub data: Vec, + pub module: u32, + pub ecc: u8, + pub cols: Option, + pub rows: Option, +} + +#[derive(Clone, Debug)] +pub struct LogoItem { + pub data: Vec, + pub width: usize, + pub height: usize, +} + +#[derive(Clone, Debug)] +pub struct PrintArea { + pub left: f32, + pub width: f32, +} + #[derive(Clone, Debug)] pub enum Item { Text(TextItem), @@ -86,7 +124,14 @@ pub enum Item { Cut { partial: bool }, Drawer { t: u8 }, Barcode(BarcodeItem), + Barcode2D(Barcode2DItem), SetLineSpacing(Option), + SetPos(f32), + MoveX(f32), + SetLeftMargin(f32), + SetPrintArea(PrintArea), + StoreLogo(LogoItem), + PrintLogo { scale_x: usize, scale_y: usize }, Init, } @@ -94,6 +139,8 @@ pub enum Item { pub struct ParsedDoc { pub items: Vec, pub summary: Vec, + pub transmit: Vec>, + pub memory: PrinterMemory, } pub struct Parser { @@ -109,8 +156,23 @@ pub struct Parser { hri: u8, items: Vec, summary: Vec, + transmit: Vec>, n_ignore: u32, fed_blank: bool, + page_mode: bool, + page_height: usize, + page_dir: u8, + logo: Option, + density: u8, + user_font: Vec<(u8, Vec)>, + qr_data: Vec, + qr_module: u32, + qr_ecc: u8, + pdf_data: Vec, + pdf_module: u32, + pdf_cols: Option, + pdf_rows: Option, + pdf_ecc: Option, } impl Default for Parser { @@ -128,20 +190,49 @@ impl Default for Parser { hri: 0, items: Vec::new(), summary: Vec::new(), + transmit: Vec::new(), n_ignore: 0, fed_blank: false, + page_mode: false, + page_height: 0, + page_dir: 0, + logo: None, + density: 0, + user_font: Vec::new(), + qr_data: Vec::new(), + qr_module: 3, + qr_ecc: 49, + pdf_data: Vec::new(), + pdf_module: 2, + pdf_cols: None, + pdf_rows: None, + pdf_ecc: None, } } } impl Parser { + #[cfg_attr(not(test), allow(dead_code))] pub fn parse(data: &[u8]) -> ParsedDoc { + Self::parse_with(data, &PrinterMemory::default()) + } + + pub fn parse_with(data: &[u8], memory: &PrinterMemory) -> ParsedDoc { let mut p = Parser::default(); + p.logo = memory.logo.clone(); + p.user_font = memory.user_font.clone(); + p.density = memory.density; p.run(data); p.flush_line(); ParsedDoc { items: p.items, summary: p.summary, + transmit: p.transmit, + memory: PrinterMemory { + logo: p.logo, + user_font: p.user_font, + density: p.density, + }, } } @@ -169,6 +260,13 @@ impl Parser { } i = ni; } + 0x1C => { + let ni = self.handle_fs(data, i); + if ni == usize::MAX { + break; + } + i = ni; + } 0x0A | 0x0C => { self.flush_line(); self.push(Item::FeedLines(1)); @@ -189,6 +287,18 @@ impl Parser { i += 1; } b if (0x20..=0x7E).contains(&b) || b >= 0x80 => { + if b >= 0xC0 { + let len = utf8_len(b); + if i + len <= data.len() + && data[i + 1..i + len].iter().all(|&c| c & 0xC0 == 0x80) + { + if let Ok(s) = std::str::from_utf8(&data[i..i + len]) { + self.push_utf8(s); + i += len; + continue; + } + } + } self.push_char(b); i += 1; } @@ -210,6 +320,10 @@ impl Parser { fn push_char(&mut self, b: u8) { let ch = codepages::decode(self.codepage, b); + self.push_char_str(ch); + } + + fn push_char_str(&mut self, ch: char) { match self.line.last_mut() { Some(last) if last.style == self.style => last.text.push(ch), _ => self.line.push(TextRun { @@ -219,6 +333,12 @@ impl Parser { } } + fn push_utf8(&mut self, s: &str) { + for ch in s.chars() { + self.push_char_str(ch); + } + } + fn handle_tab(&mut self) { // advance to next tab stop (default 8 chars) let total: usize = self.line.iter().map(|r| r.text.chars().count()).sum(); @@ -255,6 +375,17 @@ impl Parser { self.bar_module = 2; self.bar_height = 50; self.hri = 0; + self.page_mode = false; + self.page_height = 0; + self.page_dir = 0; + self.qr_data.clear(); + self.qr_module = 3; + self.qr_ecc = 49; + self.pdf_data.clear(); + self.pdf_module = 2; + self.pdf_cols = None; + self.pdf_rows = None; + self.pdf_ecc = None; } fn handle_esc(&mut self, data: &[u8], i: usize) -> usize { @@ -275,7 +406,9 @@ impl Parser { self.style.bold = n & 0x08 != 0; self.style.size_y = if n & 0x10 != 0 { 2 } else { 1 }; self.style.size_x = if n & 0x20 != 0 { 2 } else { 1 }; + self.style.reverse = n & 0x40 != 0; self.style.underline = n & 0x80 != 0; + self.style.underline2 = n & 0x80 != 0; i + 3 } else { usize::MAX @@ -299,12 +432,75 @@ impl Parser { } b'-' => { if let Some(n) = get(2) { - self.style.underline = n & 0x01 != 0; + self.style.underline = n & 0x01 != 0 || n & 0x02 != 0; + self.style.underline2 = n & 0x02 != 0; i + 3 } else { usize::MAX } } + b'4' => { + self.style.italic = true; + i + 2 + } + b'5' => { + self.style.italic = false; + i + 2 + } + b' ' => { + if let Some(n) = get(2) { + self.style.char_spacing = n; + i + 3 + } else { + usize::MAX + } + } + b'$' => { + if let (Some(a), Some(b)) = (get(2), get(3)) { + let x = (a as f32) + (b as f32) * 256.0; + self.flush_line(); + self.push(Item::SetPos(x)); + i + 4 + } else { + usize::MAX + } + } + b'\\' => { + if let (Some(a), Some(b)) = (get(2), get(3)) { + let mut dx = a as i32 + (b as i32) * 256; + if dx >= 2048 { + dx -= 4096; + } + self.flush_line(); + self.push(Item::MoveX(dx as f32)); + i + 4 + } else { + usize::MAX + } + } + b'v' => { + if let Some(n) = get(2) { + self.flush_line(); + self.push(Item::Cut { + partial: n != 0 && n != 48, + }); + i + 3 + } else { + usize::MAX + } + } + b'=' => { + self.summary.push("Modo host ESC = ignorado".to_string()); + i + 2 + } + b'7' => { + self.summary.push("ESC 7 (modo página) ignorado".to_string()); + i + 2 + } + b'8' => { + self.summary.push("ESC 8 (desactivación de página) ignorado".to_string()); + i + 2 + } b'a' => { if let Some(n) = get(2) { self.align = match n { @@ -349,6 +545,17 @@ impl Parser { usize::MAX } } + 0x0C => { + self.flush_line(); + if self.page_mode { + self.summary.push("Modo página impreso (ESC FF)".to_string()); + self.push(Item::FeedDots(self.page_height as u32)); + self.page_mode = false; + } else { + self.push(Item::FeedLines(1)); + } + i + 2 + } b't' => { if let Some(n) = get(2) { self.codepage = codepages::table(n); @@ -364,6 +571,15 @@ impl Parser { usize::MAX } } + b'r' => { + if let Some(_n) = get(2) { + self.summary + .push("ESC r (selección de color/cinta) ignorado".to_string()); + i + 3 + } else { + usize::MAX + } + } b'i' => { self.flush_line(); self.push(Item::FeedDots(6)); @@ -447,38 +663,96 @@ impl Parser { b'l' => { if let (Some(a), Some(b)) = (get(2), get(3)) { self.left_margin = (a as i32) + (b as i32) * 256; + self.flush_line(); + self.push(Item::SetLeftMargin(self.left_margin as f32)); i + 4 } else { usize::MAX } } + b'L' => { + if let (Some(a), Some(b)) = (get(2), get(3)) { + self.left_margin = (a as i32) + (b as i32) * 256; + self.flush_line(); + self.push(Item::SetLeftMargin(self.left_margin as f32)); + i + 4 + } else { + usize::MAX + } + } + b'W' => { + if let (Some(_x), Some(_xh), Some(_y), Some(_yh), Some(dx), Some(dxh), Some(_dy), Some(_dyh)) = + (get(2), get(3), get(4), get(5), get(6), get(7), get(8), get(9)) + { + let left = (get(2).unwrap() as f32) + (get(3).unwrap() as f32) * 256.0; + let width = (dx as f32) + (dxh as f32) * 256.0; + self.flush_line(); + self.push(Item::SetPrintArea(PrintArea { left, width })); + i + 10 + } else { + usize::MAX + } + } b'Q' => { if let (Some(_a), Some(_b)) = (get(2), get(3)) { + self.summary.push("ESC Q (margen derecho) ignorado".to_string()); i + 4 } else { usize::MAX } } b'c' => { - if let Some(_n) = get(2) { + if let Some(n) = get(2) { + self.summary.push(format!("Selector ESC c n={n} ignorado")); i + 3 } else { usize::MAX } } + b'&' => { + if let (Some(n), Some(c1), Some(c2)) = (get(2), get(3), get(4)) { + let count = (c2 as usize).saturating_sub(c1 as usize).saturating_add(1) * n as usize; + let data_len = count * 12; + let total = i + 5 + data_len; + if total <= data.len() { + let ds = i + 5; + for c in 0..count { + let code = c1.wrapping_add(c as u8); + let start = ds + c * 12; + self.user_font.push((code, data[start..start + 12].to_vec())); + } + self.summary.push(format!( + "Fuente descargada ESC & ({} chars x 12 bytes) almacenada", + count + )); + total + } else { + usize::MAX + } + } else { + usize::MAX + } + } b'(' => { if let (Some(m), Some(nl), Some(nh)) = (get(2), get(3), get(4)) { let len = nl as u32 + nh as u32 * 256; - self.summary.push(format!( - "Comando multi-byte ESC ( {m} ({len} bytes) ignorado" - )); + if m == b'C' && get(5) == Some(0x06) { + // ESC ( C pL pH 06 n m t: zumbador (buzzer) + self.summary.push(format!( + "Buzzer activado (ESC ( C fn=0x06, {len} bytes)" + )); + } else { + self.summary.push(format!( + "Comando multi-byte ESC ( {m} ({len} bytes) ignorado" + )); + } self.n_ignore = len; i + 5 } else { usize::MAX } } - b'K' | b'L' | b'Y' | b'Z' | b'X' | b'W' | b'U' | b'T' => { + b'K' | b'Y' | b'Z' | b'X' | b'U' | b'T' => { if let (Some(nl), Some(nh)) = (get(2), get(3)) { let len = nl as u32 + nh as u32 * 256; self.summary.push(format!( @@ -515,6 +789,102 @@ impl Parser { usize::MAX } } + b'_' => { + if let Some(n) = get(2) { + self.style.underline = n & 0x01 != 0 || n & 0x02 != 0; + self.style.underline2 = n & 0x02 != 0; + i + 3 + } else { + usize::MAX + } + } + b'B' => { + if let Some(n) = get(2) { + self.style.reverse = n & 0x01 != 0; + i + 3 + } else { + usize::MAX + } + } + b'b' => { + if let Some(n) = get(2) { + self.style.reverse = n == 1; + i + 3 + } else { + usize::MAX + } + } + b' ' => { + if let Some(n) = get(2) { + self.style.char_spacing = n; + i + 3 + } else { + usize::MAX + } + } + b'$' => { + if let (Some(a), Some(b)) = (get(2), get(3)) { + let x = (a as f32) + (b as f32) * 256.0; + self.flush_line(); + self.push(Item::SetPos(x)); + i + 4 + } else { + usize::MAX + } + } + b'\\' => { + if let (Some(a), Some(b)) = (get(2), get(3)) { + let mut dx = a as i32 + (b as i32) * 256; + if dx >= 2048 { + dx -= 4096; + } + self.flush_line(); + self.push(Item::MoveX(dx as f32)); + i + 4 + } else { + usize::MAX + } + } + b'V' => { + if let Some(n) = get(2) { + self.flush_line(); + let partial = n != 0 && n != 48; + self.push(Item::Cut { partial }); + if n == 66 || n == 67 { + if let Some(feed) = get(3) { + self.push(Item::FeedLines(feed as u32)); + i + 4 + } else { + usize::MAX + } + } else { + i + 3 + } + } else { + usize::MAX + } + } + b'/' => { + if let Some(n) = get(2) { + let (sx, sy) = logo_scale(n); + self.push_print_logo(sx, sy); + i + 3 + } else { + usize::MAX + } + } + b'a' => { + if let Some(n) = get(2) { + if n == 1 { + self.summary.push("ASB activado (estado automático)".to_string()); + } else { + self.summary.push("ASB desactivado".to_string()); + } + i + 3 + } else { + usize::MAX + } + } b'v' => { if get(2) == Some(0x30) { if let (Some(m), Some(xl), Some(xh), Some(yl), Some(yh)) = @@ -554,10 +924,14 @@ impl Parser { } if j < data.len() { let bytes = data[i + 3..j].to_vec(); + let (norm, warn) = crate::barcode::validate(m, &bytes); + if let Some(w) = warn { + self.summary.push(w); + } self.flush_line(); self.push(Item::Barcode(BarcodeItem { m, - data: bytes, + data: norm, hri: self.hri, module: self.bar_module, height: self.bar_height, @@ -570,10 +944,14 @@ impl Parser { let len = n as usize; if i + 4 + len <= data.len() { let bytes = data[i + 4..i + 4 + len].to_vec(); + let (norm, warn) = crate::barcode::validate(m, &bytes); + if let Some(w) = warn { + self.summary.push(w); + } self.flush_line(); self.push(Item::Barcode(BarcodeItem { m, - data: bytes, + data: norm, hri: self.hri, module: self.bar_module, height: self.bar_height, @@ -629,19 +1007,57 @@ impl Parser { } } b'P' => { - self.summary.push("Modo página GS P activado (ignorado)".to_string()); - i + 2 + if let (Some(xl), Some(xh), Some(yl), Some(yh)) = + (get(2), get(3), get(4), get(5)) + { + let w = xl as usize + xh as usize * 256; + let h = yl as usize + yh as usize * 256; + self.flush_line(); + self.page_mode = true; + self.page_height = h; + self.push(Item::SetPrintArea(PrintArea { + left: self.left_margin as f32, + width: w as f32, + })); + self.summary.push(format!("Modo página GS P ({w}x{h}) activado")); + i + 6 + } else { + usize::MAX + } + } + b'T' => { + if let Some(n) = get(2) { + self.page_dir = n; + if n != 0 { + self.summary + .push(format!("GS T dirección {n}: no soportada, se usa 0")); + } + i + 3 + } else { + usize::MAX + } + } + b'8' => { + return self.handle_gs_8(data, i); } b'L' => { if let (Some(a), Some(b)) = (get(2), get(3)) { self.left_margin = (a as i32) + (b as i32) * 256; + self.flush_line(); + self.push(Item::SetLeftMargin(self.left_margin as f32)); i + 4 } else { usize::MAX } } b'W' => { - if let (Some(_a), Some(_b)) = (get(2), get(3)) { + if let (Some(a), Some(b)) = (get(2), get(3)) { + let width = (a as f32) + (b as f32) * 256.0; + self.flush_line(); + self.push(Item::SetPrintArea(PrintArea { + left: self.left_margin as f32, + width, + })); i + 4 } else { usize::MAX @@ -660,21 +1076,678 @@ impl Parser { } } b'(' => { - if let (Some(m), Some(nl), Some(nh)) = (get(2), get(3), get(4)) { - let len = nl as u32 + nh as u32 * 256; - self.summary.push(format!( - "Comando multi-byte GS ( {m} ({len} bytes) ignorado" - )); - self.n_ignore = len; + return self.handle_gs_paren(data, i); + } + _ => { + self.summary.push(format!("Comando GS {:#04x} no soportado", cmd)); + i + 2 + } + } + } + + fn handle_gs_paren(&mut self, data: &[u8], i: usize) -> usize { + let get = |off: usize| data.get(i + off).copied(); + let (Some(p_l), Some(p_h)) = (get(3), get(4)) else { + return usize::MAX; + }; + let len = p_l as usize + p_h as usize * 256; + let total = i + 5 + len; + if total > data.len() { + return usize::MAX; + } + let Some(cn) = get(5) else { + return usize::MAX; + }; + let Some(fn_) = get(6) else { + return usize::MAX; + }; + let n16 = |o: usize| -> usize { + let a = data.get(i + o).copied().unwrap_or(0) as usize; + let b = data.get(i + o + 1).copied().unwrap_or(0) as usize; + a + b * 256 + }; + let n24 = |o: usize| -> usize { + let a = data.get(i + o).copied().unwrap_or(0) as usize; + let b = data.get(i + o + 1).copied().unwrap_or(0) as usize; + let c = data.get(i + o + 2).copied().unwrap_or(0) as usize; + a + b * 256 + c * 65536 + }; + match cn { + 49 => { + match fn_ { + 49 => {} + 50 => { + if let Some(n) = get(7) { + self.qr_module = (n as u32).max(1); + } + } + 51 => { + if let Some(n) = get(7) { + self.qr_ecc = n; + } + } + 67 | 0xA5 => { + let dlen = n24(8); + let start = i + 11; + if start + dlen <= total { + self.qr_data = data[start..start + dlen].to_vec(); + } + if fn_ == 0xA5 { + self.push_2d(Barcode2DKind::Qr); + } + } + 80 => { + self.push_2d(Barcode2DKind::Qr); + } + 81 | 0xA7 => { + if !self.qr_data.is_empty() { + self.transmit.push(self.qr_data.clone()); + self.summary.push(format!( + "QR transmitido ({len} bytes -> {} de datos)", + self.qr_data.len() + )); + } else { + self.summary.push("QR transmitir: sin datos".to_string()); + } + } + _ => { + self.summary.push(format!("GS ( k QR fn={fn_:#x} ignorado")); + } + } + } + 48 => { + match fn_ { + 48 => { + self.pdf_cols = Some((n16(7).clamp(1, 30)) as u8); + } + 49 => { + self.pdf_rows = Some((n16(7).clamp(3, 90)) as u8); + } + 50 => { + if let Some(n) = get(7) { + self.pdf_ecc = Some(n); + } + } + 51 => { + if let Some(n) = get(7) { + self.pdf_module = (n as u32).max(1); + } + } + 67 => { + let dlen = n24(8); + let start = i + 11; + if start + dlen <= total { + self.pdf_data = data[start..start + dlen].to_vec(); + } + } + 80 => { + self.push_2d(Barcode2DKind::Pdf417); + } + 81 => { + if !self.pdf_data.is_empty() { + self.transmit.push(self.pdf_data.clone()); + self.summary.push(format!( + "PDF417 transmitido ({len} bytes -> {} de datos)", + self.pdf_data.len() + )); + } else { + self.summary.push("PDF417 transmitir: sin datos".to_string()); + } + } + _ => { + self.summary.push(format!("GS ( k PDF417 fn={fn_:#x} ignorado")); + } + } + } + 0x4C => { + // GS ( L : definición/impresión de imágenes NV (logotipos) + match fn_ { + 48 => { + let a = get(7).unwrap_or(0); + let k = get(8).unwrap_or(0); + if a == 48 { + // a=48: definir k logotipos: por cada uno rL rH xL xH yL yH datos + let mut j = i + 9; + let mut stored = 0; + for _ in 0..k.max(1) { + if j + 6 > total { + break; + } + let r = data[j] as usize + data[j + 1] as usize * 256; + let x_bytes = data[j + 2] as usize + data[j + 3] as usize * 256; + let height = data[j + 4] as usize + data[j + 5] as usize * 256; + let ds = j + 6; + if x_bytes > 0 && height > 0 && r >= 4 && ds + x_bytes * height <= total { + self.summary.push(format!( + "Logo NV {stored} almacenado ({x_bytes} bytes x {height})" + )); + self.store_logo(data[ds..ds + x_bytes * height].to_vec(), x_bytes * 8, height); + stored += 1; + } + j = ds + r; + if j > total { + break; + } + } + if stored == 0 { + self.summary.push("GS ( L fn=48: datos de logo inválidos".to_string()); + } + } else if a == 49 { + self.logo = None; + self.summary.push("Logo NV borrado (GS ( L fn=48 a=49)".to_string()); + } else { + self.summary.push(format!("GS ( L fn=48 a={a} ignorado")); + } + } + 65 | 66 => { + self.push_print_logo(1, 1); + } + 73 | 74 | 75 => { + let n = if self.logo.is_some() { 1 } else { 0 }; + self.summary.push(format!("GS ( L fn={fn_} verificación: {n} logo(s)")); + } + _ => { + self.summary.push(format!("GS ( L fn={fn_:#x} ignorado")); + } + } + } + 0x45 => { + if fn_ == 3 { + if let Some(n) = get(7) { + self.density = n; + self.summary.push(format!("Densidad de impresión GS ( E: {n}")); + } + } else { + self.summary.push("GS ( E (densidad de impresión) ignorado".to_string()); + } + } + 0x41 => { + // GS ( A : definir caracteres descargados por el usuario + if fn_ == 48 { + let n2 = get(8).unwrap_or(0); + let a = get(9).unwrap_or(0); + let ds = i + 10; + let data_len = n2 as usize * 12; + if ds + data_len <= total { + let mut count = 0; + for c in 0..n2 as usize { + let code = a.wrapping_add(c as u8); + let start = ds + c * 12; + self.user_font.push((code, data[start..start + 12].to_vec())); + count += 1; + } + self.summary.push(format!( + "Fuente descargada GS ( A: {count} chars almacenados" + )); + } else { + self.summary.push("GS ( A: datos de fuente incompletos".to_string()); + } + } else { + self.summary.push(format!("GS ( A fn={fn_:#x} ignorado")); + } + } + _ => { + self.summary.push(format!( + "Comando multi-byte GS ( {cn} ({len} bytes) ignorado" + )); + } + } + total + } + + fn push_2d(&mut self, kind: Barcode2DKind) { + let (data, module, ecc, cols, rows) = match kind { + Barcode2DKind::Qr => (self.qr_data.clone(), self.qr_module, self.qr_ecc, None, None), + Barcode2DKind::Pdf417 => ( + self.pdf_data.clone(), + self.pdf_module, + self.pdf_ecc.unwrap_or(8), + self.pdf_cols, + self.pdf_rows, + ), + }; + if data.is_empty() { + self.summary.push(format!("{kind:?}: sin datos almacenados")); + return; + } + self.flush_line(); + self.push(Item::Barcode2D(Barcode2DItem { + kind, + data, + module, + ecc, + cols, + rows, + })); + } + + fn store_logo(&mut self, data: Vec, width: usize, height: usize) { + let l = LogoItem { data, width, height }; + self.logo = Some(l.clone()); + self.flush_line(); + self.push(Item::StoreLogo(l)); + } + + // Imprime el logo almacenado (en memoria o en la memoria NV persistente). + fn push_print_logo(&mut self, scale_x: usize, scale_y: usize) { + if let Some(l) = self.logo.clone() { + self.flush_line(); + self.push(Item::StoreLogo(l.clone())); + self.push(Item::PrintLogo { scale_x, scale_y }); + } else { + self.summary + .push("Impresión de logo: no hay logo NV almacenado".to_string()); + } + } + + fn handle_fs(&mut self, data: &[u8], i: usize) -> usize { + let get = |off: usize| data.get(i + off).copied(); + let cmd = match get(1) { + Some(c) => c, + None => return usize::MAX, + }; + match cmd { + b'q' => { + // FS q n: define NV bit images + let Some(n) = get(2) else { + return usize::MAX; + }; + let mut j = i + 3; + let mut stored = false; + let count = n.max(1) as usize; + for _ in 0..count { + if j + 4 > data.len() { + break; + } + let wb = data[j] as usize + data[j + 1] as usize * 256; + let h = data[j + 2] as usize + data[j + 3] as usize * 256; + let dlen = wb * h; + j += 4; + if j + dlen > data.len() { + break; + } + if !stored && wb > 0 && h > 0 { + self.summary.push(format!( + "Logo NV almacenado via FS q ({wb} bytes x {h})" + )); + self.store_logo(data[j..j + dlen].to_vec(), wb * 8, h); + stored = true; + } + j += dlen; + } + j + } + b'p' => { + if let Some(_n) = get(2) { + let m = get(3).unwrap_or(0); + let (sx, sy) = logo_scale(m); + self.push_print_logo(sx, sy); + i + 4 + } else { + usize::MAX + } + } + b'&' | b'"' => { + self.summary.push(format!("Kanji FS {} ignorado", cmd as char)); + i + 3 + } + _ => { + self.summary.push(format!("Comando FS {:#04x} no soportado", cmd)); + i + 2 + } + } + } + + // GS 8 L (0x1D 0x38 0x4C) y GS 8 H (0x1D 0x38 0x48): logotipos raster NV + fn handle_gs_8(&mut self, data: &[u8], i: usize) -> usize { + let get = |off: usize| data.get(i + off).copied(); + match get(2) { + Some(b'L') => { + if let (Some(n1), Some(n2), Some(xl), Some(xh), Some(yl), Some(yh)) = + (get(3), get(4), get(5), get(6), get(7), get(8)) + { + let n = n1 as usize + n2 as usize * 256; + let width_bytes = xl as usize + xh as usize * 256; + let height = yl as usize + yh as usize * 256; + let total = i + 9 + n; + if total <= data.len() && width_bytes * height <= n { + self.summary.push(format!( + "Logo raster NV almacenado via GS 8 L ({} bytes x {})", + width_bytes, height + )); + let start = i + 9; + self.store_logo( + data[start..start + width_bytes * height].to_vec(), + width_bytes * 8, + height, + ); + } + total + } else { + usize::MAX + } + } + Some(b'H') => { + if let Some(m) = get(3) { + if m == 1 { + self.summary.push( + "GS 8 H: logo en buffer de impresión (se imprime al imprimir)".to_string(), + ); + } + self.push_print_logo(1, 1); i + 5 } else { usize::MAX } } _ => { - self.summary.push(format!("Comando GS {:#04x} no soportado", cmd)); + self.summary.push("Comando GS 8 desconocido".to_string()); i + 2 } } } } + +fn utf8_len(b: u8) -> usize { + if b >= 0xF0 { + 4 + } else if b >= 0xE0 { + 3 + } else if b >= 0xC0 { + 2 + } else { + 1 + } +} + +fn logo_scale(m: u8) -> (usize, usize) { + match m { + 49 => (2, 1), + 50 => (1, 2), + 51 => (2, 2), + _ => (1, 1), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn push_esc(d: &mut Vec, cmd: u8) { + d.push(0x1B); + d.push(cmd); + } + + fn push_gs(d: &mut Vec, cmd: u8) { + d.push(0x1D); + d.push(cmd); + } + + #[test] + fn estilo_bits_esc_exclamacion() { + let mut d = Vec::new(); + push_esc(&mut d, b'!'); + d.push(0x50); // font A + size_y2 (0x10) + reverse (0x40) + d.extend_from_slice(b"X\n"); + let doc = Parser::parse(&d); + let Item::Text(t) = &doc.items[0] else { + panic!("esperaba texto"); + }; + assert!(t.runs[0].style.reverse); + assert_eq!(t.runs[0].style.size_y, 2); + } + + #[test] + fn cursiva_espaciado_y_subrayado() { + let mut d = Vec::new(); + push_esc(&mut d, b'4'); + push_esc(&mut d, b' '); + d.push(4); + d.extend_from_slice(b"A\n"); + push_esc(&mut d, b'5'); + push_esc(&mut d, b'-'); + d.push(2); + d.extend_from_slice(b"B\n"); + let doc = Parser::parse(&d); + let texts: Vec<_> = doc + .items + .iter() + .filter_map(|it| match it { + Item::Text(t) => Some(t), + _ => None, + }) + .collect(); + assert_eq!(texts.len(), 2); + assert!(texts[0].runs[0].style.italic); + assert_eq!(texts[0].runs[0].style.char_spacing, 4); + assert!(!texts[1].runs[0].style.italic); + assert!(texts[1].runs[0].style.underline && texts[1].runs[0].style.underline2); + } + + #[test] + fn posicionamiento_y_margenes() { + let mut d = Vec::new(); + push_esc(&mut d, b'$'); + d.extend_from_slice(&40u16.to_le_bytes()); + push_esc(&mut d, b'l'); + d.extend_from_slice(&(20u16).to_le_bytes()); + push_gs(&mut d, b'L'); + d.extend_from_slice(&(30u16).to_le_bytes()); + push_gs(&mut d, b'W'); + d.extend_from_slice(&(300u16).to_le_bytes()); + d.push(0x0A); + let doc = Parser::parse(&d); + let has_pos = doc.items.iter().any(|it| matches!(it, Item::SetPos(x) if *x == 40.0)); + assert!(has_pos, "esperaba Item::SetPos(40)"); + let has_margin = doc.items.iter().any(|it| matches!(it, Item::SetLeftMargin(x) if *x == 30.0)); + assert!(has_margin, "esperaba Item::SetLeftMargin(30)"); + let has_area = doc + .items + .iter() + .any(|it| matches!(it, Item::SetPrintArea(p) if p.left == 30.0 && p.width == 300.0)); + assert!(has_area, "esperaba Item::SetPrintArea"); + } + + #[test] + fn corte_gs_v() { + let mut d = Vec::new(); + push_gs(&mut d, b'V'); + d.push(1); + let doc = Parser::parse(&d); + let has_cut = doc.items.iter().any(|it| matches!(it, Item::Cut { partial: true })); + assert!(has_cut); + } + + #[test] + fn qr_store_y_print() { + let mut d = Vec::new(); + let qr = b"test-qr"; + let pl = 6 + qr.len(); + d.extend_from_slice(&[0x1D, 0x28, 0x6B]); + d.push(pl as u8); + d.push(0); + d.extend_from_slice(&[0x31, 0x43, 0x30]); + d.extend_from_slice(&(qr.len() as u16).to_le_bytes()); + d.push(0); + d.extend_from_slice(qr); + d.extend_from_slice(&[0x1D, 0x28, 0x6B, 0x02, 0x00, 0x31, 0x50]); + let doc = Parser::parse(&d); + let qr_items: Vec<_> = doc + .items + .iter() + .filter_map(|it| match it { + Item::Barcode2D(b) if b.kind == Barcode2DKind::Qr => Some(b), + _ => None, + }) + .collect(); + assert_eq!(qr_items.len(), 1); + assert_eq!(qr_items[0].data, qr); + } + + #[test] + fn pdf417_store_y_print() { + let mut d = Vec::new(); + let pdf = b"test-pdf417"; + let pl = 6 + pdf.len(); + d.extend_from_slice(&[0x1D, 0x28, 0x6B]); + d.push(pl as u8); + d.push(0); + d.extend_from_slice(&[0x30, 0x43, 0x30]); + d.extend_from_slice(&(pdf.len() as u16).to_le_bytes()); + d.push(0); + d.extend_from_slice(pdf); + d.extend_from_slice(&[0x1D, 0x28, 0x6B, 0x02, 0x00, 0x30, 0x50]); + let doc = Parser::parse(&d); + let pdf_items: Vec<_> = doc + .items + .iter() + .filter_map(|it| match it { + Item::Barcode2D(b) if b.kind == Barcode2DKind::Pdf417 => Some(b), + _ => None, + }) + .collect(); + assert_eq!(pdf_items.len(), 1); + assert_eq!(pdf_items[0].data, pdf); + } + + #[test] + fn utf8_multibyte() { + let mut d = Vec::new(); + d.extend_from_slice("Café € 40".as_bytes()); + d.push(0x0A); + let doc = Parser::parse(&d); + let Item::Text(t) = &doc.items[0] else { + panic!("esperaba texto"); + }; + assert_eq!(t.runs[0].text, "Café € 40"); + } + + #[test] + fn utf8_invalido_cae_a_codepage() { + // 0xC9 0xC4 en CP437 = '╔' '─'; 0xC4 no es byte de continuación UTF-8 + let doc = Parser::parse(&[0xC9, 0xC4, 0x0A]); + let texts: Vec<_> = doc + .items + .iter() + .filter_map(|it| match it { + Item::Text(t) => Some(t), + _ => None, + }) + .collect(); + assert_eq!(texts.len(), 1); + assert_eq!(texts[0].runs[0].text, "╔─"); + } + + #[test] + fn gs_a_asb() { + let mut d = Vec::new(); + push_gs(&mut d, b'a'); + d.push(1); + d.push(0x0A); + let doc = Parser::parse(&d); + assert!(doc.summary.iter().any(|s| s.contains("ASB"))); + } + + #[test] + fn gs_8_l_almacena_raster_y_gs_8_h_imprime() { + let mut d = Vec::new(); + const RN: usize = 2 * 4; // 2 bytes/row x 4 rows + d.extend_from_slice(&[0x1D, 0x38, 0x4C]); + d.push(RN as u8); + d.push(0); + d.extend_from_slice(&[2u8, 0, 4, 0]); // width_bytes=2 (16 px), height=4 + d.extend_from_slice(&[0u8; RN]); + d.extend_from_slice(&[0x1D, 0x38, 0x48, 0x00, 0x00]); + let doc = Parser::parse(&d); + assert!( + doc.items + .iter() + .any(|it| matches!(it, Item::StoreLogo(l) if l.width == 16 && l.height == 4)), + "esperaba Item::StoreLogo" + ); + assert!( + doc.items + .iter() + .any(|it| matches!(it, Item::PrintLogo { scale_x: 1, scale_y: 1 })), + "esperaba Item::PrintLogo" + ); + } + + #[test] + fn gs_slash_escalas_de_logo() { + let mut d = Vec::new(); + // Primero se define un logo NV (GS 8 L) y luego se imprime escalado con GS / + const RN: usize = 2 * 4; + d.extend_from_slice(&[0x1D, 0x38, 0x4C]); + d.push(RN as u8); + d.push(0); + d.extend_from_slice(&[2u8, 0, 4, 0]); + d.extend_from_slice(&[0u8; RN]); + push_gs(&mut d, b'/'); + d.push(49); + push_gs(&mut d, b'/'); + d.push(51); + let doc = Parser::parse(&d); + let scales: Vec<_> = doc + .items + .iter() + .filter_map(|it| match it { + Item::PrintLogo { scale_x, scale_y } => Some((*scale_x, *scale_y)), + _ => None, + }) + .collect(); + assert_eq!(scales, vec![(2, 1), (2, 2)]); + } + + #[test] + fn modo_pagina_gs_p_y_esc_ff() { + let mut d = Vec::new(); + d.extend_from_slice(&[0x1D, 0x50]); + d.extend_from_slice(&(576u16).to_le_bytes()); + d.extend_from_slice(&(300u16).to_le_bytes()); + d.extend_from_slice(b"PAGINA\n"); + d.extend_from_slice(&[0x1B, 0x0C]); + let doc = Parser::parse(&d); + assert!( + doc.summary.iter().any(|s| s.contains("Modo página GS P")), + "esperaba entrada de modo página" + ); + assert!( + doc.summary.iter().any(|s| s.contains("Modo página impreso")), + "esperaba salida de modo página" + ); + let has_area = doc + .items + .iter() + .any(|it| matches!(it, Item::SetPrintArea(p) if p.width == 576.0)); + assert!(has_area, "esperaba Item::SetPrintArea del modo página"); + } + + #[test] + fn transmitir_qr_y_pdf417_fn_81() { + let mut d = Vec::new(); + let qr = b"tx-data"; + let pl = 6 + qr.len(); + d.extend_from_slice(&[0x1D, 0x28, 0x6B]); + d.push(pl as u8); + d.push(0); + d.extend_from_slice(&[0x31, 0x43, 0x30]); + d.extend_from_slice(&(qr.len() as u16).to_le_bytes()); + d.push(0); + d.extend_from_slice(qr); + d.extend_from_slice(&[0x1D, 0x28, 0x6B, 0x02, 0x00, 0x31, 0x51]); + let doc = Parser::parse(&d); + assert_eq!(doc.transmit, vec![qr.to_vec()]); + } + + #[test] + fn buzzer_y_fuente_descargada() { + let mut d = Vec::new(); + d.extend_from_slice(&[0x1B, 0x28, 0x43, 0x04, 0x00, 0x06, 0x03, 0x01, 0x00]); + d.extend_from_slice(&[0x1B, 0x26, 0x01, 0x41, 0x42]); + d.extend_from_slice(&[0u8; 24]); + let doc = Parser::parse(&d); + assert!(doc.summary.iter().any(|s| s.contains("Buzzer"))); + assert!(doc.summary.iter().any(|s| s.contains("Fuente descargada"))); + } +} diff --git a/src/render.rs b/src/render.rs index f09d8ba..8e02838 100644 --- a/src/render.rs +++ b/src/render.rs @@ -2,7 +2,9 @@ use std::path::Path; use ab_glyph::{point, Font, FontVec, PxScale, ScaleFont}; -use crate::parser::{Alignment, FontSel, Item, TextItem, TextRun}; +use crate::parser::{ + Alignment, Barcode2DItem, Barcode2DKind, FontSel, Item, LogoItem, TextItem, TextRun, +}; pub struct Fonts { pub regular: Option, @@ -59,6 +61,9 @@ pub struct Renderer<'a> { cv: Canvas, fonts: &'a Fonts, line_spacing: Option, + left_margin: f32, + print_width: Option, + logo: Option, } struct Canvas { @@ -68,6 +73,7 @@ struct Canvas { x: f32, y: f32, line_height: f32, + clip_right: i32, } impl Canvas { @@ -79,6 +85,7 @@ impl Canvas { x: 0.0, y: 0.0, line_height: 24.0, + clip_right: i32::MAX, } } @@ -91,7 +98,7 @@ impl Canvas { } fn set(&mut self, x: i32, y: i32, cov: f32) { - if x < 0 || y < 0 { + if x < 0 || y < 0 || x >= self.clip_right { return; } let xi = x as usize; @@ -114,6 +121,29 @@ impl Canvas { } } + fn erase(&mut self, x: i32, y: i32, cov: f32) { + if x < 0 || y < 0 || x >= self.clip_right { + return; + } + let xi = x as usize; + let yi = y as usize; + if xi >= self.w { + return; + } + self.ensure(yi + 1); + if yi + 1 > self.used { + self.used = yi + 1; + } + let idx = (yi * self.w + xi) * 4; + let cur = self.buf[idx]; + let nv = (cur as f32 + (255.0 - cur as f32) * cov) as u8; + if nv > cur { + self.buf[idx] = nv; + self.buf[idx + 1] = nv; + self.buf[idx + 2] = nv; + } + } + fn fill(&mut self, x0: i32, y0: i32, x1: i32, y1: i32) { for y in y0..y1 { for x in x0..x1 { @@ -121,6 +151,14 @@ impl Canvas { } } } + + fn erase_fill(&mut self, x0: i32, y0: i32, x1: i32, y1: i32) { + for y in y0..y1 { + for x in x0..x1 { + self.erase(x, y, 1.0); + } + } + } } pub fn render(items: &[Item], width: usize, fonts: &Fonts) -> RenderedPage { @@ -128,6 +166,9 @@ pub fn render(items: &[Item], width: usize, fonts: &Fonts) -> RenderedPage { cv: Canvas::new(width), fonts, line_spacing: None, + left_margin: 0.0, + print_width: None, + logo: None, }; r.cv.ensure(1024); for item in items { @@ -173,29 +214,53 @@ impl<'a> Renderer<'a> { self.cv.y += 4.0; } Item::Barcode(b) => self.render_barcode(b), + Item::Barcode2D(b) => self.render_2d(b), Item::SetLineSpacing(sp) => { self.line_spacing = sp.map(|n| n as f32); if let Some(n) = sp { self.cv.line_height = *n as f32; } } + Item::SetPos(x) => { + self.cv.x = (self.left_margin + x).max(self.left_margin); + } + Item::MoveX(dx) => { + self.cv.x = (self.cv.x + dx).max(self.left_margin); + } + Item::SetLeftMargin(x) => { + self.left_margin = x.max(0.0); + self.cv.x = self.left_margin; + } + Item::SetPrintArea(pa) => { + self.left_margin = pa.left.max(0.0); + self.print_width = Some(pa.width.max(0.0)); + self.cv.clip_right = (pa.left + pa.width).max(0.0) as i32; + self.cv.x = self.left_margin; + } + Item::StoreLogo(l) => { + self.logo = Some(l.clone()); + } + Item::PrintLogo { scale_x, scale_y } => { + if let Some(l) = &self.logo.clone() { + self.render_logo(l, *scale_x, *scale_y); + } + } Item::Init => { self.cv.y += 8.0; + self.left_margin = 0.0; + self.print_width = None; + self.cv.clip_right = i32::MAX; self.cv.x = 0.0; } } } fn render_text(&mut self, item: &TextItem) { - let mut total = 0.0f32; - for run in &item.runs { - total += self.measure_run(run); - } - let ox = match item.align { - Alignment::Left => 0.0, - Alignment::Center => ((self.cv.w as f32 - total) / 2.0).max(0.0), - Alignment::Right => (self.cv.w as f32 - total).max(0.0), - }; + let avail = self.print_width.unwrap_or(self.cv.w as f32 - self.left_margin); + let lines = self.wrap_lines(item, avail); + if lines.is_empty() { + return; + } let item_h = item .runs .iter() @@ -203,25 +268,79 @@ impl<'a> Renderer<'a> { .fold(0.0f32, f32::max); let advance = self.line_spacing.unwrap_or(item_h).max(4.0); if let Some(f) = &self.fonts.regular { - let mut x = ox; let asc = self.ascent_px(item_h); - let baseline = self.cv.y + asc; - for run in &item.runs { - self.draw_run(f, run, &mut x, baseline); + for (line, width) in &lines { + let baseline = self.cv.y + asc; + let ox = self.line_ox(item.align, *width, avail); + let mut x = ox; + for (ri, text) in line { + let mut run = item.runs[*ri].clone(); + run.text = text.clone(); + self.draw_run(f, &run, &mut x, baseline); + } + self.cv.y += advance; } } else { - let mut x = ox; - for run in &item.runs { - self.draw_run_fallback(run, &mut x); + for (line, width) in &lines { + let ox = self.line_ox(item.align, *width, avail); + let mut x = ox; + for (ri, text) in line { + let mut run = item.runs[*ri].clone(); + run.text = text.clone(); + self.draw_run_fallback(&run, &mut x); + } + self.cv.y += advance; } } - self.cv.y += advance; self.cv.line_height = advance; - self.cv.x = 0.0; + self.cv.x = self.left_margin; self.cv.ensure(self.cv.y as usize + 64); self.cv.used = self.cv.used.max(self.cv.y as usize); } + fn line_ox(&self, align: Alignment, width: f32, avail: f32) -> f32 { + match align { + Alignment::Center => self.left_margin + ((avail - width) / 2.0).max(0.0), + Alignment::Right => self.left_margin + (avail - width).max(0.0), + Alignment::Left => self.cv.x, + } + } + + fn char_advances(&self, run: &TextRun) -> Vec { + // Paso fijo tipo térmica: cada carácter ocupa una celda de ancho sx + let (sx, _sy) = self.scaled_scale(run); + let spacing = run.style.char_spacing as f32; + run.text.chars().map(|_| sx + spacing).collect() + } + + fn wrap_lines(&self, item: &TextItem, avail: f32) -> Vec<(Vec<(usize, String)>, f32)> { + let mut out: Vec<(Vec<(usize, String)>, f32)> = Vec::new(); + let mut cur: Vec<(usize, String)> = Vec::new(); + let mut cur_w = 0.0f32; + for (ri, run) in item.runs.iter().enumerate() { + let advs = self.char_advances(run); + let chars: Vec = run.text.chars().collect(); + let mut seg = String::new(); + for (ci, &ch) in chars.iter().enumerate() { + let a = advs[ci]; + if !cur.is_empty() && cur_w + a > avail { + cur.push((ri, std::mem::take(&mut seg))); + out.push((std::mem::take(&mut cur), cur_w)); + cur_w = 0.0; + } + seg.push(ch); + cur_w += a; + } + if !seg.is_empty() { + cur.push((ri, seg)); + } + } + if !cur.is_empty() { + out.push((cur, cur_w)); + } + out + } + fn sy_for(&self, run: &TextRun) -> f32 { let base = match run.style.font { FontSel::A => 24.0, @@ -262,7 +381,9 @@ impl<'a> Renderer<'a> { fn measure_run(&self, run: &TextRun) -> f32 { let (sx, _sy) = self.scaled_scale(run); - run.text.chars().count() as f32 * sx + let n = run.text.chars().count() as f32; + let spacing = run.style.char_spacing as f32; + (n * sx) + (n * spacing) } fn draw_run(&mut self, f: &FontVec, run: &TextRun, x: &mut f32, baseline: f32) { @@ -275,6 +396,16 @@ impl<'a> Renderer<'a> { }; let scaled = font.as_scaled(PxScale { x: sx, y: sy }); let run_x0 = *x; + let run_w = self.measure_run(run); + let spacing = run.style.char_spacing as f32; + if run.style.reverse { + let asc = scaled.ascent(); + let desc = scaled.descent(); + let y0 = (baseline - asc - 1.0) as i32; + let y1 = (baseline - desc + 2.0) as i32; + let x1 = (run_x0 + run_w) as i32; + self.cv.fill(run_x0 as i32, y0, x1, y1); + } for ch in run.text.chars() { let gid = scaled.glyph_id(ch); let glyph = gid.with_scale_and_position( @@ -286,23 +417,52 @@ impl<'a> Renderer<'a> { let gx = bounds.min.x; let gy = bounds.min.y; outlined.draw(|dx, dy, cov| { - let px = (gx + dx as f32) as i32; + let shear = if run.style.italic { + (dy as f32 - sy * 0.5) * 0.3 + } else { + 0.0 + }; + let px = (gx + dx as f32 + shear) as i32; let py = (gy + dy as f32) as i32; - self.cv.set(px, py, cov); - if fake_bold { - self.cv.set(px + 1, py, cov); - } - if run.style.double_strike { - self.cv.set(px, py + 1, cov); + if run.style.reverse { + self.cv.erase(px, py, cov); + if fake_bold { + self.cv.erase(px + 1, py, cov); + } + if run.style.double_strike { + self.cv.erase(px, py + 1, cov); + } + } else { + self.cv.set(px, py, cov); + if fake_bold { + self.cv.set(px + 1, py, cov); + } + if run.style.double_strike { + self.cv.set(px, py + 1, cov); + } } }); } - *x += scaled.h_advance(gid); + *x += sx + spacing; } if run.style.underline { + let h = if run.style.underline2 { + (sy * 0.3).max(2.0) + } else { + (sy * 0.15).max(1.0) + }; let y0 = (baseline + 1.0) as i32; - let y1 = (baseline + 1.0 + (sy * 0.15).max(1.0)) as i32; - self.cv.fill(run_x0 as i32, y0, *x as i32, y1); + let y1 = (baseline + 1.0 + h) as i32; + let x1 = if run.style.reverse { + (run_x0 + run_w) as i32 + } else { + *x as i32 + }; + if run.style.reverse { + self.cv.erase_fill(run_x0 as i32, y0, x1, y1); + } else { + self.cv.fill(run_x0 as i32, y0, x1, y1); + } } } @@ -326,6 +486,16 @@ impl<'a> Renderer<'a> { let scale_x = self.sx_aim(run).max(1.0) as i32; let scale_y = self.sy_for(run) as i32; let baseline = self.cv.y as i32 + scale_y; + let spacing = run.style.char_spacing as i32; + if run.style.reverse { + let run_w = self.measure_run(run) as i32; + self.cv.fill( + *x as i32, + baseline - scale_y, + *x as i32 + run_w, + baseline + 2, + ); + } for ch in run.text.chars() { let glyph = font8x8::unicode::BASIC_UNICODE .iter() @@ -337,16 +507,15 @@ impl<'a> Renderer<'a> { if glyph[row] & (0x80 >> col) != 0 { let x0 = *x as i32 + col as i32 * scale_x; let y0 = baseline - (8 - row) as i32 * scale_y; - self.cv.fill( - x0, - y0, - x0 + scale_x, - y0 + scale_y, - ); + if run.style.reverse { + self.cv.erase_fill(x0, y0, x0 + scale_x, y0 + scale_y); + } else { + self.cv.fill(x0, y0, x0 + scale_x, y0 + scale_y); + } } } } - *x += 8.0 * scale_x as f32; + *x += 8.0 * scale_x as f32 + spacing as f32; } } @@ -369,7 +538,7 @@ impl<'a> Renderer<'a> { } } self.cv.y += ri.height as f32; - self.cv.x = 0.0; + self.cv.x = self.left_margin; self.cv.used = self.cv.used.max(self.cv.y as usize); } @@ -398,27 +567,53 @@ impl<'a> Renderer<'a> { } } self.cv.y += height as f32; - self.cv.x = 0.0; + self.cv.x = self.left_margin; self.cv.used = self.cv.used.max(self.cv.y as usize); } fn render_barcode(&mut self, b: &crate::parser::BarcodeItem) { let module = b.module.max(1) as i32; let bar_h = b.height.max(8) as i32; - let data_w = b.data.len() as i32 * 8 * module; + let modules: Option> = match b.m { + 67 => crate::barcode::ean13_modules(&b.data), + 68 => crate::barcode::ean8_modules(&b.data), + 65 => crate::barcode::upca_as_ean13(&b.data) + .and_then(|d| crate::barcode::ean13_modules(&d)), + 73 => crate::barcode::code128_modules(&b.data), + 70 => crate::barcode::itf_modules(&b.data), + 69 => crate::barcode::code39_modules(&b.data), + _ => None, + }; + let data_w = if let Some(m) = &modules { + m.len() as i32 * module + } else { + b.data.len() as i32 * 8 * module + }; let ox = (self.cv.w as i32 - data_w).max(0) / 2; self.cv.ensure(self.cv.y as usize + bar_h as usize); let y0 = self.cv.y as i32; - for (i, byte) in b.data.iter().enumerate() { - for bit in 0..8u32 { - if byte & (0x80 >> bit) != 0 { - let x0 = ox + (i as i32 * 8 + bit as i32) * module; - self.cv.fill(x0, y0, x0 + module, y0 + bar_h); + match modules { + Some(m) => { + for (i, bar) in m.iter().enumerate() { + if *bar { + let x0 = ox + i as i32 * module; + self.cv.fill(x0, y0, x0 + module, y0 + bar_h); + } + } + } + None => { + for (i, byte) in b.data.iter().enumerate() { + for bit in 0..8u32 { + if byte & (0x80 >> bit) != 0 { + let x0 = ox + (i as i32 * 8 + bit as i32) * module; + self.cv.fill(x0, y0, x0 + module, y0 + bar_h); + } + } } } } self.cv.y += bar_h as f32; - self.cv.x = 0.0; + self.cv.x = self.left_margin; self.cv.used = self.cv.used.max(self.cv.y as usize); if b.hri != 0 { let text: String = b @@ -429,6 +624,67 @@ impl<'a> Renderer<'a> { self.draw_text_plain(&text, true); } } + + fn render_2d(&mut self, b: &Barcode2DItem) { + let bitmap = match b.kind { + Barcode2DKind::Qr => crate::barcode2d::qr(&b.data, b.ecc), + Barcode2DKind::Pdf417 => crate::barcode2d::pdf417(&b.data, b.cols, b.rows, Some(b.ecc)), + }; + let Some(bm) = bitmap else { + return; + }; + let module = b.module.max(1) as usize; + let quiet = match b.kind { + Barcode2DKind::Qr => 4, + Barcode2DKind::Pdf417 => 2, + }; + let total_w = (bm.width + quiet * 2) * module; + let total_h = (bm.height + quiet * 2) * module; + let ox = ((self.cv.w as i32 - total_w as i32).max(0)) / 2; + let y0 = self.cv.y as i32; + self.cv.ensure(self.cv.y as usize + total_h); + let m = module as i32; + let q = quiet as i32; + for row in 0..bm.height { + for col in 0..bm.width { + if bm.dark[row * bm.width + col] { + let x0 = ox + (col as i32 + q) * m; + let yy0 = y0 + (row as i32 + q) * m; + self.cv.fill(x0, yy0, x0 + m, yy0 + m); + } + } + } + self.cv.y += total_h as f32; + self.cv.x = self.left_margin; + self.cv.used = self.cv.used.max(self.cv.y as usize); + } + + fn render_logo(&mut self, l: &LogoItem, scale_x: usize, scale_y: usize) { + let w = l.width * scale_x.max(1); + let h = l.height * scale_y.max(1); + let ox = (self.cv.w as i32 - w as i32).max(0) / 2; + self.cv.ensure(self.cv.y as usize + h); + let row_bytes = (l.width + 7) / 8; + for row in 0..l.height { + let ro = row * row_bytes; + for col in 0..l.width { + let byte = l.data[ro + col / 8]; + let bit = 7 - (col % 8); + if byte & (1 << bit) != 0 { + let x0 = ox + (col * scale_x) as i32; + let y0 = self.cv.y as i32 + (row * scale_y) as i32; + for sy in 0..scale_y { + for sx in 0..scale_x { + self.cv.set(x0 + sx as i32, y0 + sy as i32, 1.0); + } + } + } + } + } + self.cv.y += h as f32; + self.cv.x = self.left_margin; + self.cv.used = self.cv.used.max(self.cv.y as usize); + } } /// Downscale an RGBA image (nearest neighbour) so the height fits `max_h`. diff --git a/src/sample.rs b/src/sample.rs index e2067ed..586bd3a 100644 --- a/src/sample.rs +++ b/src/sample.rs @@ -5,6 +5,33 @@ pub fn demo_receipt() -> Vec { push_esc(&mut d, b't'); d.push(0); + // Consultas de estado en tiempo real (el servidor responde al socket) + d.extend_from_slice(&[0x10, 0x04, 0x01, 0x10, 0x04, 0x05, 0x10, 0x05, 0x1D, 0x72, 0x01]); + + // Código QR (GS ( k 49) + let qr_text = b"TIENDA DEMO POS|TICKET 001|11,60 EUR"; + let qr_pl = 6 + qr_text.len(); + d.extend_from_slice(&[0x1D, 0x28, 0x6B]); + d.push(qr_pl as u8); + d.push(0); + d.extend_from_slice(&[0x31, 0x43, 0x30]); + d.extend_from_slice(&(qr_text.len() as u16).to_le_bytes()); + d.push(0); + d.extend_from_slice(qr_text); + d.extend_from_slice(&[0x1D, 0x28, 0x6B, 0x02, 0x00, 0x31, 0x50]); + + // PDF417 (GS ( k 48) + let pdf_text = b"DEMO PDF417|1234567890|EUR 11.60"; + let pdf_pl = 6 + pdf_text.len(); + d.extend_from_slice(&[0x1D, 0x28, 0x6B]); + d.push(pdf_pl as u8); + d.push(0); + d.extend_from_slice(&[0x30, 0x43, 0x30]); + d.extend_from_slice(&(pdf_text.len() as u16).to_le_bytes()); + d.push(0); + d.extend_from_slice(pdf_text); + d.extend_from_slice(&[0x1D, 0x28, 0x6B, 0x02, 0x00, 0x30, 0x50]); + push_esc(&mut d, b'a'); d.push(1); push_esc(&mut d, b'E'); @@ -29,6 +56,40 @@ pub fn demo_receipt() -> Vec { d.push(1); d.extend_from_slice(b"--------------------------------\n"); + // Logo NV definido con FS q e impreso con FS p + let lw: u16 = 32; + let lh: u16 = 16; + d.extend_from_slice(&[0x1C, 0x71, 0x01]); + d.extend_from_slice(&lw.to_le_bytes()); + d.extend_from_slice(&lh.to_le_bytes()); + let mut logo = vec![0u8; ((lw as usize + 7) / 8) * lh as usize]; + for y in 0..lh as usize { + for x in 0..lw as usize { + if (x / 4 + y) % 3 != 0 { + logo[y * ((lw as usize + 7) / 8) + x / 8] |= 0x80 >> (x % 8); + } + } + } + d.extend_from_slice(&logo); + d.extend_from_slice(&[0x1C, 0x70, 0x00, 0x00]); + push_esc(&mut d, b'd'); + d.push(1); + + // Inverso, cursiva y subrayado doble + push_gs(&mut d, b'B'); + d.push(1); + d.extend_from_slice(b"PROMOCION SEMANA 20%\n"); + push_gs(&mut d, b'B'); + d.push(0); + push_esc(&mut d, b'4'); + d.extend_from_slice(b"* Oferta valida en caja *\n"); + push_esc(&mut d, b'5'); + push_esc(&mut d, b'-'); + d.push(2); + d.extend_from_slice(b"CONDICIONES EN TIENDA\n"); + push_esc(&mut d, b'-'); + d.push(0); + push_esc(&mut d, b'a'); d.push(2); d.extend_from_slice(b"TOTAL A PAGAR\n"); @@ -66,6 +127,15 @@ pub fn demo_receipt() -> Vec { d.push(67); d.extend_from_slice(b"1234567"); d.push(0); + // Code 128 (m=73) e ITF (m=70) con módulos reales + push_gs(&mut d, b'k'); + d.push(73); + d.extend_from_slice(b"ABC-128"); + d.push(0); + push_gs(&mut d, b'k'); + d.push(70); + d.extend_from_slice(b"123456"); + d.push(0); push_esc(&mut d, b'd'); d.push(3); @@ -89,7 +159,59 @@ pub fn demo_receipt() -> Vec { push_esc(&mut d, b'd'); d.push(4); - push_esc(&mut d, b'i'); + + // ASB (estado automático) + transmitir QR y PDF417 (GS ( k fn 81) + d.extend_from_slice(&[0x1D, 0x61, 0x01]); + d.extend_from_slice(&[0x1D, 0x28, 0x6B, 0x02, 0x00, 0x31, 0x51]); + d.extend_from_slice(&[0x1D, 0x28, 0x6B, 0x02, 0x00, 0x30, 0x51]); + + // Logo raster NV con GS 8 L e impresión escalada GS / 49 (doble ancho) + let rlw: u16 = 96; + let rlh: u16 = 8; + let rwb = (rlw as usize + 7) / 8; // ancho en bytes (12) -> 96 px + let rn = rwb * rlh as usize; + d.extend_from_slice(&[0x1D, 0x38, 0x4C]); + d.push((rn & 0xFF) as u8); + d.push((rn >> 8) as u8); + d.extend_from_slice(&(rwb as u16).to_le_bytes()); + d.extend_from_slice(&(rlh as u16).to_le_bytes()); + let mut raster = vec![0u8; rn]; + for y in 0..rlh as usize { + for x in 0..rlw as usize { + if (x / 6 + y) % 2 == 0 { + raster[y * rwb + x / 8] |= 0x80 >> (x % 8); + } + } + } + d.extend_from_slice(&raster); + d.extend_from_slice(&[0x1D, 0x38, 0x48, 0x00, 0x00]); + d.extend_from_slice(&[0x1D, 0x2F, 0x31]); + push_esc(&mut d, b'd'); + d.push(2); + + // Modo página GS P + texto + ESC FF + d.extend_from_slice(&[0x1D, 0x50]); + d.extend_from_slice(&(576u16).to_le_bytes()); + d.extend_from_slice(&(300u16).to_le_bytes()); + push_esc(&mut d, b'$'); + d.extend_from_slice(&(40u16).to_le_bytes()); + d.extend_from_slice(b"BLOQUE DE PAGINA\n"); + push_esc(&mut d, b'a'); + d.push(1); + d.extend_from_slice(b"Etiqueta en modo pagina\n"); + push_esc(&mut d, b'!'); + d.push(0); + d.push(0x0C); + + // Buzzer ESC ( C fn=06 + d.extend_from_slice(&[0x1B, 0x28, 0x43, 0x04, 0x00, 0x06, 0x03, 0x01, 0x00]); + + // Fuente descargada ESC & (1 char 'A', 12 bytes) + d.extend_from_slice(&[0x1B, 0x26, 0x01, 0x41, 0x42]); + d.extend_from_slice(&[0u8; 24]); + + push_gs(&mut d, b'V'); + d.push(49); d } @@ -103,3 +225,25 @@ fn push_gs(d: &mut Vec, cmd: u8) { d.push(0x1D); d.push(cmd); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn demo_se_parsea_y_renderea() { + let d = demo_receipt(); + let doc = crate::parser::Parser::parse(&d); + let fonts = crate::render::Fonts::load(); + let page = crate::render::render(&doc.items, 576, &fonts); + assert!(page.height > 50, "la demo debe producir contenido"); + assert!( + doc.summary.iter().any(|s| s.contains("Buzzer")), + "la demo debe activar el zumbador" + ); + assert!( + doc.summary.iter().any(|s| s.contains("Modo página GS P")), + "la demo debe entrar en modo página" + ); + } +} diff --git a/src/server.rs b/src/server.rs index 20f6be1..a0e883c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -65,27 +65,166 @@ fn handle_connection( let _ = stream.set_read_timeout(Some(READ_TIMEOUT)); let mut data: Vec = Vec::new(); let mut buf = [0u8; 8192]; + let mut pending: Vec = Vec::new(); + // Estado automático (ASB): cuando se activa, un hilo vigila los cambios de + // estado y los envía por el socket sin necesidad de que llegue más datos. + let mut asb_on = false; + let pusher_stop = Arc::new(AtomicBool::new(false)); + let mut pusher: Option> = None; loop { + let mut chunk = Vec::with_capacity(pending.len() + buf.len()); + chunk.extend(pending.drain(..)); match stream.read(&mut buf) { Ok(0) => break, - Ok(n) => data.extend_from_slice(&buf[..n]), + Ok(n) => chunk.extend_from_slice(&buf[..n]), Err(_) => break, } + let mut clean = Vec::with_capacity(chunk.len()); + let mut i = 0; + while i < chunk.len() { + match chunk[i] { + 0x10 => { + if i + 1 < chunk.len() { + match chunk[i + 1] { + 0x05 => { + // DLE ENQ: estado de impresora + let sim = state.lock().unwrap().sim; + let _ = stream.write_all(&[sim.printer_status()]); + i += 2; + continue; + } + 0x04 => { + if i + 2 < chunk.len() { + let sim = state.lock().unwrap().sim; + let resp = match chunk[i + 2] { + 1 => sim.printer_status(), + 2 => sim.gs_r2(), + 3 | 4 => sim.error_status(), + 5 => sim.paper_sensor(), + 6 => sim.recovery_status(), + 7 | 8 => sim.paper_sensor(), + 10..=19 => 0x00, // estado de búfer (BSB): vacío + _ => 0x00, + }; + let _ = stream.write_all(&[resp]); + i += 3; + continue; + } else { + pending.push(chunk[i]); + break; + } + } + _ => { + clean.push(chunk[i]); + i += 1; + continue; + } + } + } else { + pending.push(0x10); + break; + } + } + 0x1D => { + if i + 1 < chunk.len() && chunk[i + 1] == 0x72 { + if i + 2 < chunk.len() { + let n = chunk[i + 2]; + let sim = state.lock().unwrap().sim; + let resp = match n { + 49 | 50 => sim.paper_sensor(), + 2 => sim.gs_r2(), + _ => sim.gs_r1(), + }; + let _ = stream.write_all(&[resp]); + i += 3; + continue; + } else { + pending.extend_from_slice(&chunk[i..]); + break; + } + } else if i + 1 < chunk.len() && chunk[i + 1] == 0x61 { + // GS a n: activar/desactivar estado automático (ASB) + if i + 2 < chunk.len() { + let n = chunk[i + 2]; + if n == 1 && !asb_on { + asb_on = true; + let sim = state.lock().unwrap().sim; + let _ = stream.write_all(&sim.asb()); + if let Ok(mut w) = stream.try_clone() { + let st = state.clone(); + let stop = pusher_stop.clone(); + // El empujador parte de la misma instantánea ya + // respondida, para no perder cambios posteriores. + pusher = Some(thread::spawn(move || { + let mut last = Some(sim); + loop { + if stop.load(Ordering::SeqCst) { + break; + } + let sim = st.lock().unwrap().sim; + if last != Some(sim) { + if w.write_all(&sim.asb()).is_err() { + break; + } + last = Some(sim); + } + thread::sleep(Duration::from_millis(50)); + } + })); + } + } else if n == 0 && asb_on { + asb_on = false; + pusher_stop.store(true, Ordering::SeqCst); + if let Some(h) = pusher.take() { + let _ = h.join(); + } + } + i += 3; + continue; + } else { + pending.extend_from_slice(&chunk[i..]); + break; + } + } + clean.push(chunk[i]); + i += 1; + } + _ => { + clean.push(chunk[i]); + i += 1; + } + } + } + data.extend(clean); + } + pusher_stop.store(true, Ordering::SeqCst); + if let Some(h) = pusher.take() { + let _ = h.join(); } - let _ = stream.shutdown(std::net::Shutdown::Both); - let (page, summary, paper) = { - let s = state.lock().unwrap(); + let (page, summary, paper, transmit) = { + let mut s = state.lock().unwrap(); let paper = s.paper_width; - let doc = Parser::parse(&data); + let memory = s.memory.clone(); + let doc = Parser::parse_with(&data, &memory); + // Persistir la memoria NV entre trabajos (logos, fuentes, densidad) + s.memory = doc.memory.clone(); + // Guardarla también en disco para que sobreviva a la sesión + let _ = crate::nvstore::save(&s.memory); let page: Option = if data.is_empty() { None } else { Some(render::render(&doc.items, paper, &fonts)) }; - (page, doc.summary, paper) + (page, doc.summary, paper, doc.transmit) }; + for t in &transmit { + let _ = stream.write_all(t); + } + let _ = stream.flush(); + let _ = stream.shutdown(std::net::Shutdown::Both); + let mut s = state.lock().unwrap(); let id = s.next_id; s.next_id += 1; @@ -117,3 +256,176 @@ pub fn poke_to_stop(port: u16) { let _ = c.shutdown(std::net::Shutdown::Both); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::state::SharedState; + use std::sync::Mutex; + use std::time::Instant; + + fn free_port() -> u16 { + let l = TcpListener::bind(("0.0.0.0", 0)).unwrap(); + l.local_addr().unwrap().port() + } + + // Redirigir la persistencia NV a un fichero temporal en las pruebas para + // no tocar el NV real del usuario. + fn nv_tmp_path() -> std::path::PathBuf { + std::env::temp_dir().join(format!("pos_nv_socket_{}", std::process::id())).join("nv.bin") + } + + fn roundtrip(port: u16, payload: &[u8]) -> Vec { + let mut c = TcpStream::connect(("127.0.0.1", port)).unwrap(); + c.set_read_timeout(Some(Duration::from_secs(4))).unwrap(); + c.write_all(payload).unwrap(); + // Cerrar solo la escritura para que el servidor vea EOF limpio + c.shutdown(std::net::Shutdown::Write).unwrap(); + let mut out = Vec::new(); + let mut buf = [0u8; 256]; + loop { + match c.read(&mut buf) { + Ok(0) => break, + Ok(n) => out.extend_from_slice(&buf[..n]), + Err(_) => break, + } + } + out + } + + #[test] + fn estado_y_transmit_por_socket() { + unsafe { std::env::set_var("POS_NV_PATH", nv_tmp_path()) }; + let state = Arc::new(Mutex::new(SharedState::new())); + let fonts = Arc::new(Fonts::load()); + let stop = Arc::new(AtomicBool::new(false)); + let port = free_port(); + let ctx = egui::Context::default(); + let handle = spawn(state.clone(), fonts, port, stop.clone(), ctx); + thread::sleep(Duration::from_millis(500)); + { + let s = state.lock().unwrap(); + eprintln!("server_error = {:?}", s.server_error); + } + + // Consultas de estado + ASB + QR store + transmit fn 81 + let mut payload: Vec = vec![ + 0x10, 0x04, 0x01, // DLE EOT 1 -> printer status + 0x10, 0x04, 0x05, // DLE EOT 5 -> papel + 0x1D, 0x72, 0x01, // GS r 1 -> online + 0x1D, 0x61, 0x01, // GS a 1 -> ASB + ]; + payload.extend_from_slice(&[0x1D, 0x28, 0x6B, 0x09, 0x00, 0x31, 0x43, 0x30]); + payload.extend_from_slice(&[0x03, 0x00, 0x00, 0x58, 0x59, 0x5A]); // datos "XYZ" + payload.extend_from_slice(&[0x1D, 0x28, 0x6B, 0x02, 0x00, 0x31, 0x51]); // fn 81 + + let resp = roundtrip(port, &payload); + let expected: Vec = vec![0x36, 0x00, 0x18, 0x36, 0x00, 0x00, 0x00, b'X', b'Y', b'Z']; + assert_eq!(resp, expected, "estado online + transmit"); + + // Con papel agotado y error: los bits de estado deben cambiar + { + let mut s = state.lock().unwrap(); + s.sim.paper_out = true; + s.sim.error = true; + } + let resp2 = roundtrip(port, &[0x10, 0x04, 0x01, 0x1D, 0x72, 0x01, 0x1D, 0x61, 0x01]); + assert_eq!(resp2, vec![0x23, 0x00, 0x23, 0x01, 0x0C, 0x00], "estado con fallo"); + + stop.store(true, Ordering::SeqCst); + poke_to_stop(port); + let _ = handle.join(); + } + + #[test] + fn dle_eot_completo() { + unsafe { std::env::set_var("POS_NV_PATH", nv_tmp_path()) }; + let state = Arc::new(Mutex::new(SharedState::new())); + let fonts = Arc::new(Fonts::load()); + let stop = Arc::new(AtomicBool::new(false)); + let port = free_port(); + let ctx = egui::Context::default(); + let handle = spawn(state.clone(), fonts, port, stop.clone(), ctx); + thread::sleep(Duration::from_millis(500)); + + // n=1 estado, n=2 offline, n=3/4 error, n=5/7/8 papel, n=6 recovery, n=10 búfer + let mut payload: Vec = Vec::new(); + for n in [1, 2, 3, 4, 5, 6, 7, 8, 10] { + payload.extend_from_slice(&[0x10, 0x04, n]); + } + let resp = roundtrip(port, &payload); + // online por defecto: 0x36, resto 0x00 + assert_eq!(resp, vec![0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + + // Con papel agotado y error: EOT 2/3/4/5/6 cambian + { + let mut s = state.lock().unwrap(); + s.sim.paper_out = true; + s.sim.error = true; + } + let resp2 = roundtrip(port, &[0x10, 0x04, 2, 0x10, 0x04, 3, 0x10, 0x04, 4, 0x10, 0x04, 5]); + // gs_r2=1 (error), error_status=0x08, papel=0x18 + assert_eq!(resp2, vec![0x01, 0x08, 0x08, 0x18]); + + stop.store(true, Ordering::SeqCst); + poke_to_stop(port); + let _ = handle.join(); + } + + #[test] + fn asb_push_por_cambio_de_estado() { + unsafe { std::env::set_var("POS_NV_PATH", nv_tmp_path()) }; + let state = Arc::new(Mutex::new(SharedState::new())); + let fonts = Arc::new(Fonts::load()); + let stop = Arc::new(AtomicBool::new(false)); + let port = free_port(); + let ctx = egui::Context::default(); + let handle = spawn(state.clone(), fonts, port, stop.clone(), ctx); + thread::sleep(Duration::from_millis(500)); + + let mut c = TcpStream::connect(("127.0.0.1", port)).unwrap(); + c.set_read_timeout(Some(Duration::from_millis(300))).unwrap(); + c.write_all(&[0x1D, 0x61, 0x01]).unwrap(); + let mut b = [0u8; 4]; + c.read_exact(&mut b).unwrap(); + assert_eq!(b, [0x36, 0x00, 0x00, 0x00], "ASB inicial al activar"); + + // Cambio de papel: la impresora empuja el nuevo ASB sin más entrada + { + let mut s = state.lock().unwrap(); + s.sim.paper_out = true; + } + let mut got = Vec::new(); + let deadline = Instant::now() + Duration::from_secs(2); + while got.len() < 4 && Instant::now() < deadline { + let mut tmp = [0u8; 4]; + match c.read(&mut tmp) { + Ok(0) => break, + Ok(n) => got.extend_from_slice(&tmp[..n]), + Err(_) => {} + } + } + assert_eq!(got, vec![0x26, 0x00, 0x0C, 0x00], "ASB push por papel agotado"); + + // Al desactivar (GS a 0) deja de empujar aunque el estado cambie + c.write_all(&[0x1D, 0x61, 0x00]).unwrap(); + thread::sleep(Duration::from_millis(200)); + { + let mut s = state.lock().unwrap(); + s.sim.paper_out = false; + s.sim.drawer_open = true; + } + thread::sleep(Duration::from_millis(400)); + let mut r = [0u8; 4]; + assert!( + c.read(&mut r).is_err(), + "no debe llegar ASB tras GS a 0" + ); + + drop(c); + stop.store(true, Ordering::SeqCst); + poke_to_stop(port); + let _ = handle.join(); + } +} + diff --git a/src/state.rs b/src/state.rs index c037a2a..3b0177b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,8 +1,18 @@ use std::collections::VecDeque; use std::sync::Mutex; +use crate::parser::LogoItem; use crate::render::RenderedPage; +// Memoria de la impresora que persiste entre trabajos (como la NV/flash real): +// logos NV, fuentes descargadas y densidad de impresión. +#[derive(Clone, Debug, Default)] +pub struct PrinterMemory { + pub logo: Option, + pub user_font: Vec<(u8, Vec)>, + pub density: u8, +} + pub struct Job { pub id: u64, pub when: String, @@ -13,6 +23,15 @@ pub struct Job { pub summary: Vec, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub struct SimState { + pub paper_out: bool, + pub near_end: bool, + pub cover_open: bool, + pub error: bool, + pub drawer_open: bool, +} + pub struct SharedState { pub jobs: Vec, pub logs: VecDeque, @@ -20,6 +39,8 @@ pub struct SharedState { pub next_id: u64, pub paper_width: usize, pub server_error: Option, + pub sim: SimState, + pub memory: PrinterMemory, } impl SharedState { @@ -31,6 +52,8 @@ impl SharedState { next_id: 1, paper_width: 576, server_error: None, + sim: SimState::default(), + memory: PrinterMemory::default(), } } @@ -47,3 +70,132 @@ pub fn now_hm() -> String { } pub type Shared = std::sync::Arc>; + +// Los layouts de bits siguen (aproximadamente) los de ESC/POS de EPSON: +// DLE EOT 1 / DLE ENQ / GS r 1 / ASB byte1: +// bit0 offline, bit1 tapa cerrada, bit2 sin error, bit4 papel presente, bit5 cajon cerrado +// DLE EOT 4: bit3 error de cabezal/recuperable +// DLE EOT 5 / GS r 49/50: 0x00 presente, 0x0C casi fin, 0x18 fin de papel +// GS r 2 / ASB byte2: bit0 offline +// ASB byte3: papel (0x00 presente, 0x04 casi fin, 0x0C fin) +// ASB byte4: bit0 cajon abierto (kick) +impl SimState { + pub fn printer_status(&self) -> u8 { + let mut s = 0x02 | 0x04 | 0x10 | 0x20; + if self.cover_open { + s &= !0x02; + } + if self.error { + s &= !0x04; + s |= 0x01; + } + if self.paper_out { + s &= !0x10; + } + if self.drawer_open { + s &= !0x20; + } + s + } + + pub fn error_status(&self) -> u8 { + if self.error { + 0x08 + } else { + 0x00 + } + } + + pub fn recovery_status(&self) -> u8 { + if self.error { + 0x01 + } else { + 0x00 + } + } + + pub fn paper_sensor(&self) -> u8 { + if self.paper_out { + 0x18 + } else if self.near_end { + 0x0C + } else { + 0x00 + } + } + + pub fn gs_r1(&self) -> u8 { + let mut s = 0x08 | 0x10; + if self.error { + s &= !0x08; + } + if self.paper_out { + s &= !0x10; + } + s + } + + pub fn gs_r2(&self) -> u8 { + if self.error || self.cover_open { + 0x01 + } else { + 0x00 + } + } + + pub fn asb(&self) -> [u8; 4] { + [ + self.printer_status(), + self.gs_r2(), + if self.paper_out { + 0x0C + } else if self.near_end { + 0x04 + } else { + 0x00 + }, + if self.drawer_open { + 0x01 + } else { + 0x00 + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn estado_online_por_defecto() { + let s = SimState::default(); + assert_eq!(s.printer_status(), 0x36); + assert_eq!(s.gs_r1(), 0x18); + assert_eq!(s.gs_r2(), 0x00); + assert_eq!(s.paper_sensor(), 0x00); + assert_eq!(s.asb(), [0x36, 0x00, 0x00, 0x00]); + } + + #[test] + fn estado_con_papel_agotado() { + let mut s = SimState::default(); + s.paper_out = true; + assert_eq!(s.printer_status() & 0x10, 0); + assert_eq!(s.paper_sensor(), 0x18); + assert_eq!(s.gs_r1() & 0x10, 0); + assert_eq!(s.asb()[2], 0x0C); + } + + #[test] + fn estado_con_error_y_tapa_abierta() { + let mut s = SimState::default(); + s.error = true; + s.cover_open = true; + assert_eq!(s.printer_status() & 0x01, 0x01); + assert_eq!(s.printer_status() & 0x02, 0); + assert_eq!(s.error_status(), 0x08); + assert_eq!(s.gs_r2(), 0x01); + assert_eq!(s.asb()[1], 0x01); + } +} diff --git a/vendor/pdf417/.cargo-ok b/vendor/pdf417/.cargo-ok new file mode 100644 index 0000000..5f8b795 --- /dev/null +++ b/vendor/pdf417/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/vendor/pdf417/.cargo_vcs_info.json b/vendor/pdf417/.cargo_vcs_info.json new file mode 100644 index 0000000..4885538 --- /dev/null +++ b/vendor/pdf417/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "0344aca7690bb6a1ac31aa954892e123b0f24055" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/vendor/pdf417/.gitignore b/vendor/pdf417/.gitignore new file mode 100644 index 0000000..4c790d0 --- /dev/null +++ b/vendor/pdf417/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +.DS_Store diff --git a/vendor/pdf417/Cargo.toml b/vendor/pdf417/Cargo.toml new file mode 100644 index 0000000..13273ef --- /dev/null +++ b/vendor/pdf417/Cargo.toml @@ -0,0 +1,36 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "pdf417" +version = "0.2.1" +authors = ["Sofiane"] +exclude = ["/images"] +description = "No-std PDF417 encoder" +documentation = "https://docs.rs/pdf417" +readme = "README.md" +keywords = [ + "pdf417", + "no-std", + "embedded", +] +categories = [ + "encoding", + "embedded", + "no-std", +] +license-file = "LICENSE" +repository = "https://github.com/Sofiman/pdf417/" + +[dependencies.awint_core] +version = "0.11.0" +default-features = false diff --git a/vendor/pdf417/Cargo.toml.orig b/vendor/pdf417/Cargo.toml.orig new file mode 100644 index 0000000..059708e --- /dev/null +++ b/vendor/pdf417/Cargo.toml.orig @@ -0,0 +1,16 @@ +[package] +name = "pdf417" +version = "0.2.1" +edition = "2021" +authors = ["Sofiane"] +description = "No-std PDF417 encoder" +documentation = "https://docs.rs/pdf417" +readme = "README.md" +repository = "https://github.com/Sofiman/pdf417/" +license-file = "LICENSE" +keywords = ["pdf417", "no-std", "embedded"] +categories = ["encoding", "embedded", "no-std"] +exclude = ["/images"] + +[dependencies] +awint_core = { version = "0.11.0", default-features = false } diff --git a/vendor/pdf417/LICENSE b/vendor/pdf417/LICENSE new file mode 100644 index 0000000..6f56ccd --- /dev/null +++ b/vendor/pdf417/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Sofiane + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/pdf417/README.md b/vendor/pdf417/README.md new file mode 100644 index 0000000..4ab9db2 --- /dev/null +++ b/vendor/pdf417/README.md @@ -0,0 +1,88 @@ +[![crates.io](https://img.shields.io/crates/v/pdf417)](https://crates.io/crates/pdf417) + +![PDF417 generated on esp32-s3 and displayed on a SSD1306 OLED screen](images/pdf417_esp23_s3_ssd1306_small.jpg) + +# PDF417 Barcode generator + +A no-std and no-alloc PDF417 encoder for embedded applications (also works for +std). This library implements mutliple encoding modes for numbers, strings and +bytes according to the specification. You can also customize the rendering of +the barcodes (size, storage and inverted) and supports both Truncated PDF417 +and MicroPDF417. + +## Usage + +### Installation + +**pdf417** is available on [crates.io](https://crates.io/crates/pdf417) + +Use the cargo add command: +```shell +cargo add pdf417 +``` + +or add the crate in your Cargo.toml: +```toml +[dependencies] +pdf417 = "0" +``` + +### Generating a PDF417 + +Before you can generate a PDF417 barcode you should consider: + +1. The **capacity** of the barcode (maximum number of codewords). +3. The **level** of error correction (0-8) +2. The **dimensions** of the barcode (number of rows and columns) + +Here is an no-std and no alloc example to generate a PDF417 barcode: + +```rust +use pdf417::*; + +const COLS: u8 = 3; +const ROWS: u8 = 5; +const WIDTH: usize = pdf417_width!(COLS); +const HEIGHT: usize = pdf417_height!(ROWS); + +// High-level encoding +let mut input = [0u16; (ROWS * COLS) as usize]; +let (level, _) = PDF417Encoder::new(&mut input, false) + .append_ascii("Hello, world!").fit_seal().unwrap(); + +// Rendering +let mut storage = [false; WIDTH * HEIGHT]; +PDF417::new(&input, ROWS, COLS, level).render(&mut storage[..]); +``` + +> If you need a bigger barcode, you can control the size of the modules (see the +> documentation) using the pdf417_width and pdf417_height macros. + +### Data Segments + +You can multiple data segments (aka encoding modes) on a single barcode. The +available types are: +- **numeric**: efficient encoding of 44+ digit numbers +- **ascii**: efficient encoding of text (alphanumeric + punctuation) with + support for non-displyable ASCII values which are encoded as raw bytes. +- **bytes**: binary data as bytes + +An additional **UTF-8** mode is available which allows encoding of UTF-8 strings +using an ECI identifier and byte encoding mode (note that this encoding takes +significantly more space than the ASCII mode). + +> See the different methods available on `PDF417Encoder` struct. + +## Documentation + +More documentation is available at [docs.rs](https://docs.rs/pdf417). + +## Contribution & Feedback + +If you have any feedback, please open an issue. If you encounter any bugs or unwanted behaviour, please open an issue. + +This projet is open to contributions, feel free to submit your pull requests! + +# License + +pdf417 is available under the MIT license. See the LICENSE file for more info. diff --git a/vendor/pdf417/examples/gs1.rs b/vendor/pdf417/examples/gs1.rs new file mode 100644 index 0000000..7c1fab1 --- /dev/null +++ b/vendor/pdf417/examples/gs1.rs @@ -0,0 +1,45 @@ +use pdf417::*; + +const WHITE: &str = "\x1B[38;2;255;255;255m█"; +const BLACK: &str = "\x1B[38;2;0;0;0m█"; + +const PADDING: usize = 4; +const COLS: u8 = 4; +const ROWS: u8 = 7; + +const W: usize = pdf417_width!(COLS); +const H: usize = pdf417_height!(ROWS); + +fn main() { + let mut input = [0u16; (COLS*ROWS) as usize]; + + // \x1d - GS, Group Separator, ASCII Code 29 (Hex 1D) + // \x1e - RS, Record Separator, ASCII Code 30 (Hex 1E) + // \x04 - EOT, End of Transmission, ASCII Code 04 (Hex 04) + let (level, _) = PDF417Encoder::new(&mut input, false) + .append_ascii("\x1e06\x1d66831000\x1d9117327\x1e\x04") + .fit_seal().unwrap(); + + let mut storage = [false; W * H]; + let pdf417 = PDF417::new(&input, ROWS, COLS, level); + pdf417.render(&mut storage[..]); + + let mut col = 0; + for _ in 0..((PADDING+1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + print!("{}", str::repeat(WHITE, PADDING)); + for on in storage { + print!("{}", if on { BLACK } else { WHITE }); + col += 1; + if col == W { + col = 0; + print!("{b}\n{b}", b = str::repeat(WHITE, PADDING)); + } + } + println!("{}", str::repeat(WHITE, W + PADDING)); + for _ in 0..((PADDING-1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + println!("\x1B[0m"); +} diff --git a/vendor/pdf417/examples/micro_pdf417.rs b/vendor/pdf417/examples/micro_pdf417.rs new file mode 100644 index 0000000..7040cb0 --- /dev/null +++ b/vendor/pdf417/examples/micro_pdf417.rs @@ -0,0 +1,42 @@ +use pdf417::*; + +const WHITE: &str = "\x1B[38;2;255;255;255m█"; +const BLACK: &str = "\x1B[38;2;0;0;0m█"; + +const PADDING: usize = 4; +const COLS: u8 = 4; +const ROWS: u8 = 4; +const V: Option = get_variant(ROWS, COLS); + +const W: usize = m_pdf417_width!(COLS); +const H: usize = m_pdf417_height!(ROWS); + +fn main() { + let variant = V.unwrap(); + let mut input = [0u16; (COLS*ROWS) as usize]; + PDF417Encoder::new(&mut input, true).append_num(12345678) + .seal(variant); + + let mut storage = [false; W * H]; + let pdf417 = MicroPDF417::new(&input, variant); + pdf417.render(&mut storage[..]); + + let mut col = 0; + for _ in 0..((PADDING+1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + print!("{}", str::repeat(WHITE, PADDING)); + for on in storage { + print!("{}", if on { BLACK } else { WHITE }); + col += 1; + if col == W { + col = 0; + print!("{b}\n{b}", b = str::repeat(WHITE, PADDING)); + } + } + println!("{}", str::repeat(WHITE, W + PADDING)); + for _ in 0..((PADDING-1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + println!("\x1B[0m"); +} diff --git a/vendor/pdf417/examples/raw.rs b/vendor/pdf417/examples/raw.rs new file mode 100644 index 0000000..d799803 --- /dev/null +++ b/vendor/pdf417/examples/raw.rs @@ -0,0 +1,45 @@ +use pdf417::*; + +const WHITE: &str = "\x1B[38;2;255;255;255m█"; +const BLACK: &str = "\x1B[38;2;0;0;0m█"; + +const PADDING: usize = 4; +const COLS: u8 = 3; +const ROWS: u8 = 4; +const LEVEL: u8 = 0; + +const INPUT: [u16; 12] = [10, 900, 7 * 30 + 4, 11 * 30 + 11, 14 * 30 + 26, 22 * 30 + 14, 17 * 30 + 11, 3 * 30 + 29, 10 * 30 + 29, 900, 0, 0]; // HELLO WORLD! +//const INPUT: [u16; 6] = [4, 900, 7 * 30 + 7, 7 * 30 + 7, 0, 0]; // HELLO WORLD! +//const INPUT: [u16; 6] = [4, 900, 19 * 30 + 4, 18 * 30 + 19, 0, 0]; // TEST +//const INPUT: [u16; 20] = [16, 902, 1, 278, 827, 900, 295, 902, 2, 326, 823, 544, 900, 149, 900, 900, 0, 0, 0, 0]; + +const W: usize = pdf417_width!(COLS); +const H: usize = pdf417_height!(ROWS); + +fn main() { + let mut input = INPUT.clone(); + ecc::generate_ecc(&mut input, LEVEL); + + let mut storage = [false; W * H]; + let pdf417 = PDF417::new(&input, ROWS, COLS, LEVEL); + pdf417.render(&mut storage[..]); + + let mut col = 0; + for _ in 0..((PADDING+1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + print!("{}", str::repeat(WHITE, PADDING)); + for on in storage { + print!("{}", if on { BLACK } else { WHITE }); + col += 1; + if col == W { + col = 0; + print!("{b}\n{b}", b = str::repeat(WHITE, PADDING)); + } + } + println!("{}", str::repeat(WHITE, W + PADDING)); + for _ in 0..((PADDING-1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + println!("\x1B[0m"); +} diff --git a/vendor/pdf417/examples/scaled.rs b/vendor/pdf417/examples/scaled.rs new file mode 100644 index 0000000..2e2d19f --- /dev/null +++ b/vendor/pdf417/examples/scaled.rs @@ -0,0 +1,45 @@ +use pdf417::*; + +const WHITE: &str = "\x1B[38;2;255;255;255m█"; +const BLACK: &str = "\x1B[38;2;0;0;0m█"; + +const PADDING: usize = 4; +const COLS: u8 = 2; +const ROWS: u8 = 5; +const SCALE: (u32, u32) = (1, 3); + +const W: usize = pdf417_width!(COLS, SCALE.0); +const H: usize = pdf417_height!(ROWS, SCALE.1); + +fn main() { + let mut input = [0u16; (COLS*ROWS) as usize]; + let (level, _) = PDF417Encoder::new(&mut input, false) + .append_ascii("Test") + .fit_seal().unwrap(); + + let mut storage = [0u8; ((W - 1) / 8 + 1) * H]; + let pdf417 = PDF417::new(&input, ROWS, COLS, level).scaled(SCALE); + pdf417.render(&mut storage[..]); + + let mut col = 0; + for _ in 0..((PADDING+1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + print!("{}", str::repeat(WHITE, PADDING)); + for bits in storage { + for k in (0..8).rev() { + if col + 7 - k == W { break; } + print!("{}", if (bits & (1 << k)) != 0 { BLACK } else { WHITE }); + } + col += 8; + if col >= W { + col = 0; + print!("{b}\n{b}", b = str::repeat(WHITE, PADDING)); + } + } + println!("{}", str::repeat(WHITE, W + PADDING)); + for _ in 0..((PADDING-1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + println!("\x1B[0m"); +} diff --git a/vendor/pdf417/examples/segments.rs b/vendor/pdf417/examples/segments.rs new file mode 100644 index 0000000..1d68db0 --- /dev/null +++ b/vendor/pdf417/examples/segments.rs @@ -0,0 +1,43 @@ +use pdf417::*; + +const WHITE: &str = "\x1B[38;2;255;255;255m█"; +const BLACK: &str = "\x1B[38;2;0;0;0m█"; + +const PADDING: usize = 4; +const COLS: u8 = 4; +const ROWS: u8 = 6; + +const W: usize = pdf417_width!(COLS); +const H: usize = pdf417_height!(ROWS); + +fn main() { + let mut input = [0u16; (COLS*ROWS) as usize]; + let (level, _) = PDF417Encoder::new(&mut input, false) + .append_ascii("AsciiSegment ") + .append_num(42) + .append_bytes(b" ByteSegment") + .fit_seal().unwrap(); + + let mut storage = [false; W * H]; + let pdf417 = PDF417::new(&input, ROWS, COLS, level); + pdf417.render(&mut storage[..]); + + let mut col = 0; + for _ in 0..((PADDING+1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + print!("{}", str::repeat(WHITE, PADDING)); + for on in storage { + print!("{}", if on { BLACK } else { WHITE }); + col += 1; + if col == W { + col = 0; + print!("{b}\n{b}", b = str::repeat(WHITE, PADDING)); + } + } + println!("{}", str::repeat(WHITE, W + PADDING)); + for _ in 0..((PADDING-1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + println!("\x1B[0m"); +} diff --git a/vendor/pdf417/examples/simple.rs b/vendor/pdf417/examples/simple.rs new file mode 100644 index 0000000..883dbb8 --- /dev/null +++ b/vendor/pdf417/examples/simple.rs @@ -0,0 +1,41 @@ +use pdf417::*; + +const WHITE: &str = "\x1B[38;2;255;255;255m█"; +const BLACK: &str = "\x1B[38;2;0;0;0m█"; + +const PADDING: usize = 4; +const COLS: u8 = 4; +const ROWS: u8 = 7; + +const W: usize = pdf417_width!(COLS); +const H: usize = pdf417_height!(ROWS); + +fn main() { + let mut input = [0u16; (COLS*ROWS) as usize]; + let (level, _) = PDF417Encoder::new(&mut input, false) + .append_ascii("Hello, world from no-std *rust* !") + .fit_seal().unwrap(); + + let mut storage = [false; W * H]; + let pdf417 = PDF417::new(&input, ROWS, COLS, level); + pdf417.render(&mut storage[..]); + + let mut col = 0; + for _ in 0..((PADDING+1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + print!("{}", str::repeat(WHITE, PADDING)); + for on in storage { + print!("{}", if on { BLACK } else { WHITE }); + col += 1; + if col == W { + col = 0; + print!("{b}\n{b}", b = str::repeat(WHITE, PADDING)); + } + } + println!("{}", str::repeat(WHITE, W + PADDING)); + for _ in 0..((PADDING-1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + println!("\x1B[0m"); +} diff --git a/vendor/pdf417/examples/truncated.rs b/vendor/pdf417/examples/truncated.rs new file mode 100644 index 0000000..3c0a22d --- /dev/null +++ b/vendor/pdf417/examples/truncated.rs @@ -0,0 +1,42 @@ +use pdf417::*; + +const WHITE: &str = "\x1B[38;2;255;255;255m█"; +const BLACK: &str = "\x1B[38;2;0;0;0m█"; + +const PADDING: usize = 4; +const COLS: u8 = 3; +const ROWS: u8 = 6; + +const W: usize = pdf417_width!(COLS, 1, true); +const H: usize = pdf417_height!(ROWS, 1); + +fn main() { + let mut input = [0u16; (COLS*ROWS) as usize]; + let (level, _) = PDF417Encoder::new(&mut input, false) + .append_ascii("Truncated PDF417") + .fit_seal().unwrap(); + + let mut storage = [false; W * H]; + let pdf417 = PDF417::new(&input, ROWS, COLS, level) + .truncated(true); + pdf417.render(&mut storage[..]); + + let mut col = 0; + for _ in 0..((PADDING+1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + print!("{}", str::repeat(WHITE, PADDING)); + for on in storage { + print!("{}", if on { BLACK } else { WHITE }); + col += 1; + if col == W { + col = 0; + print!("{b}\n{b}", b = str::repeat(WHITE, PADDING)); + } + } + println!("{}", str::repeat(WHITE, W + PADDING)); + for _ in 0..((PADDING-1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + println!("\x1B[0m"); +} diff --git a/vendor/pdf417/examples/utf8.rs b/vendor/pdf417/examples/utf8.rs new file mode 100644 index 0000000..ecd2a28 --- /dev/null +++ b/vendor/pdf417/examples/utf8.rs @@ -0,0 +1,43 @@ +use pdf417::*; + +const WHITE: &str = "\x1B[38;2;255;255;255m█"; +const BLACK: &str = "\x1B[38;2;0;0;0m█"; + +const PADDING: usize = 4; +const COLS: u8 = 4; +const ROWS: u8 = 6; +const SCALE: (u32, u32) = (1, 1); + +const W: usize = pdf417_width!(COLS, SCALE.0); +const H: usize = pdf417_height!(ROWS, SCALE.1); + +fn main() { + let mut input = [0u16; (COLS*ROWS) as usize]; + + let (level, _) = PDF417Encoder::new(&mut input, false) + .append_utf8("💛 ワンピース") + .fit_seal().unwrap(); + + let mut storage = [false; W * H]; + let pdf417 = PDF417::new(&input, ROWS, COLS, level); + pdf417.render(&mut storage[..]); + + let mut col = 0; + for _ in 0..((PADDING+1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + print!("{}", str::repeat(WHITE, PADDING)); + for on in storage { + print!("{}", if on { BLACK } else { WHITE }); + col += 1; + if col == W { + col = 0; + print!("{b}\n{b}", b = str::repeat(WHITE, PADDING)); + } + } + println!("{}", str::repeat(WHITE, W + PADDING)); + for _ in 0..((PADDING-1)/2) { + println!("{}", str::repeat(WHITE, W + PADDING * 2)); + } + println!("\x1B[0m"); +} diff --git a/vendor/pdf417/src/ecc.rs b/vendor/pdf417/src/ecc.rs new file mode 100644 index 0000000..2fed58b --- /dev/null +++ b/vendor/pdf417/src/ecc.rs @@ -0,0 +1,166 @@ +//! ECC generation for PDF417 + +use crate::tables::*; + +/// Returns the number of ECC codewords required by the specified level (0-8) +/// of a regular PDF417. +/// +/// | ECC Level | Number of ECC codewords | +/// |-----------|-------------------------| +/// | 0 | 2 | +/// | 1 | 4 | +/// | 2 | 8 | +/// | 3 | 16 | +/// | 4 | 32 | +/// | 5 | 64 | +/// | 6 | 128 | +/// | 7 | 256 | +/// | 8 | 512 | +pub const fn ecc_count(level: u8) -> usize { + assert!(level < 9, "ECC level must be between 0 and 8 inclusive"); + 1 << (level as usize + 1) +} + +/// Calculate and stores the ECC codewords in the slice `codewords` in-place. +/// The last **N** codewords are overwritten by the ECC codewords where **N** +/// is the number of ECC codewords to insert according to the specified level. +/// The ECC is calculated for the (total-N) first codewords where total is the +/// length of the codewords slice. +pub fn generate_ecc(codewords: &mut [u16], level: u8) { + assert!(level <= 8, "ECC level must be between 0 and 8 inclusive"); + let factors: &'static [u16] = match level { + 0 => &ECC_L0, + 1 => &ECC_L1, + 2 => &ECC_L2, + 3 => &ECC_L3, + 4 => &ECC_L4, + 5 => &ECC_L5, + 6 => &ECC_L6, + 7 => &ECC_L7, + 8 => &ECC_L8, + _ => unreachable!() + }; + + assert!(codewords.len() >= factors.len(), "ECC codewords could not fit in buffer"); + generate_ecc_codewords(factors, codewords); +} + +/// Calculate and stores the ECC codewords in the slice `codewords` in-place. +/// The last **N** codewords are overwritten by the ECC codewords where **N** +/// is the number of ECC codewords to insert according to the k offset +/// (depending on the variant of the MicroPDF417). The ECC is calculated for the +/// (total-N) first codewords where total is the length of the codewords slice. +pub fn generate_micro_ecc(codewords: &mut [u16], count: usize, k: usize) { + assert!(count > 0, "count cannot be empty"); + assert!(codewords.len() >= count); + generate_ecc_codewords(&ECC_MICRO[k..(k+count)], codewords); +} + +fn generate_ecc_codewords(factors: &'static [u16], codewords: &mut [u16]) { + let (data, ecc) = codewords.split_at_mut(codewords.len() - factors.len()); + ecc.fill(0); + + for cw in data { + let t = (*cw + ecc[0]) % 929; + + for i in (0..factors.len()).rev() { + let factor = ((t as usize * factors[i] as usize) % 929) as u16; + let d = if i > 0 { ecc[factors.len() - i] } else { 0 }; + ecc[factors.len() - 1 - i] = (d + 929 - factor) % 929; + } + } + + for e in ecc { + if *e != 0 { + *e = 929 - *e; + } + } +} + +#[cfg(test)] +mod tests { + use super::{generate_ecc, ecc_count}; + + const INPUT_DATA: [u16; 16] = [16, 902, 1, 278, 827, 900, 295, 902, 2, 326, 823, 544, 900, 149, 900, 900]; + + #[test] + fn test_ecc_l0() { + const EXPECTED: [u16; ecc_count(0)] = [156, 765]; + let mut data = [0u16; INPUT_DATA.len() + EXPECTED.len()]; + data[..INPUT_DATA.len()].copy_from_slice(&INPUT_DATA); + generate_ecc(&mut data, 0); + assert_eq!(data[INPUT_DATA.len()..], EXPECTED); + } + + #[test] + fn test_ecc_l1() { + const EXPECTED: [u16; ecc_count(1)] = [168, 875, 63, 355]; + let mut data = [0u16; INPUT_DATA.len() + EXPECTED.len()]; + data[..INPUT_DATA.len()].copy_from_slice(&INPUT_DATA); + generate_ecc(&mut data, 1); + assert_eq!(data[INPUT_DATA.len()..], EXPECTED); + } + + #[test] + fn test_ecc_l2() { + const EXPECTED: [u16; ecc_count(2)] = [628, 715, 393, 299, 863, 601, 169, 708]; + let mut data = [0u16; INPUT_DATA.len() + EXPECTED.len()]; + data[..INPUT_DATA.len()].copy_from_slice(&INPUT_DATA); + generate_ecc(&mut data, 2); + assert_eq!(data[INPUT_DATA.len()..], EXPECTED); + } + + #[test] + fn test_ecc_l3() { + const EXPECTED: [u16; ecc_count(3)] = [232, 176, 793, 616, 476, 406, 855, 445, 84, 518, 522, 721, 607, 2, 42, 578]; + let mut data = [0u16; INPUT_DATA.len() + EXPECTED.len()]; + data[..INPUT_DATA.len()].copy_from_slice(&INPUT_DATA); + generate_ecc(&mut data, 3); + assert_eq!(data[INPUT_DATA.len()..], EXPECTED); + } + + #[test] + fn test_ecc_l4() { + const EXPECTED: [u16; ecc_count(4)] = [281, 156, 276, 668, 44, 252, 877, 30, 549, 856, 773, 639, 420, 330, 693, 329, 283, 723, 480, 482, 102, 925, 535, 892, 374, 472, 837, 331, 343, 608, 390, 364]; + let mut data = [0u16; INPUT_DATA.len() + EXPECTED.len()]; + data[..INPUT_DATA.len()].copy_from_slice(&INPUT_DATA); + generate_ecc(&mut data, 4); + assert_eq!(data[INPUT_DATA.len()..], EXPECTED); + } + + #[test] + fn test_ecc_l5() { + const EXPECTED: [u16; ecc_count(5)] = [31, 850, 18, 870, 53, 477, 837, 130, 533, 186, 266, 450, 39, 492, 542, 653, 499, 887, 618, 103, 364, 313, 906, 396, 270, 735, 593, 81, 557, 712, 810, 48, 167, 533, 205, 577, 503, 126, 449, 189, 859, 471, 493, 849, 554, 76, 878, 893, 168, 497, 251, 704, 311, 650, 283, 268, 462, 223, 659, 763, 176, 34, 544, 304]; + let mut data = [0u16; INPUT_DATA.len() + EXPECTED.len()]; + data[..INPUT_DATA.len()].copy_from_slice(&INPUT_DATA); + generate_ecc(&mut data, 5); + assert_eq!(data[INPUT_DATA.len()..], EXPECTED); + } + + #[test] + fn test_ecc_l6() { + const EXPECTED: [u16; ecc_count(6)] = [345, 775, 909, 489, 650, 568, 869, 577, 574, 349, 885, 317, 492, 222, 783, 451, 647, 385, 168, 366, 118, 655, 643, 551, 179, 880, 880, 752, 132, 206, 765, 862, 727, 240, 32, 266, 911, 287, 813, 437, 868, 201, 681, 867, 567, 398, 508, 564, 504, 676, 785, 554, 831, 566, 424, 93, 515, 275, 61, 544, 272, 621, 374, 922, 779, 663, 789, 295, 631, 536, 755, 465, 485, 416, 76, 412, 76, 431, 28, 614, 767, 419, 600, 779, 94, 584, 647, 846, 121, 97, 790, 205, 424, 793, 263, 271, 694, 522, 437, 817, 382, 164, 113, 849, 178, 602, 554, 261, 415, 737, 401, 675, 203, 271, 649, 120, 765, 209, 522, 687, 420, 32, 60, 266, 270, 228, 304, 270]; + let mut data = [0u16; INPUT_DATA.len() + EXPECTED.len()]; + data[..INPUT_DATA.len()].copy_from_slice(&INPUT_DATA); + generate_ecc(&mut data, 6); + assert_eq!(data[INPUT_DATA.len()..], EXPECTED); + } + + #[test] + fn test_ecc_l7() { + const EXPECTED: [u16; ecc_count(7)] = [142, 203, 799, 4, 105, 137, 793, 914, 225, 636, 60, 171, 490, 180, 414, 141, 399, 599, 829, 288, 108, 268, 444, 481, 795, 146, 655, 778, 189, 32, 597, 206, 208, 711, 845, 608, 642, 636, 540, 795, 845, 466, 492, 659, 138, 800, 912, 171, 92, 438, 225, 301, 777, 449, 230, 448, 326, 182, 892, 681, 543, 582, 732, 758, 162, 587, 685, 378, 646, 356, 354, 25, 839, 839, 556, 253, 501, 771, 745, 616, 473, 293, 669, 822, 613, 684, 229, 265, 110, 438, 144, 727, 317, 605, 414, 497, 82, 278, 267, 323, 43, 894, 624, 282, 790, 579, 430, 255, 802, 553, 922, 604, 68, 692, 809, 909, 663, 589, 735, 670, 298, 158, 201, 68, 124, 64, 67, 338, 694, 373, 225, 579, 309, 699, 920, 432, 717, 72, 126, 819, 142, 755, 473, 630, 331, 758, 730, 65, 359, 451, 236, 16, 56, 31, 87, 587, 125, 385, 384, 197, 352, 383, 173, 271, 38, 558, 810, 260, 521, 680, 7, 319, 650, 334, 695, 708, 0, 562, 365, 204, 114, 185, 560, 746, 767, 449, 797, 688, 63, 135, 818, 805, 3, 536, 908, 532, 400, 698, 49, 212, 630, 93, 157, 275, 3, 20, 611, 179, 302, 282, 876, 665, 241, 206, 474, 80, 217, 460, 462, 751, 719, 571, 536, 794, 522, 385, 598, 756, 162, 212, 758, 662, 361, 223, 587, 857, 503, 382, 615, 86, 283, 541, 847, 518, 406, 736, 486, 408, 226, 342, 784, 772, 211, 888, 234, 335]; + let mut data = [0u16; INPUT_DATA.len() + EXPECTED.len()]; + data[..INPUT_DATA.len()].copy_from_slice(&INPUT_DATA); + generate_ecc(&mut data, 7); + assert_eq!(data[INPUT_DATA.len()..], EXPECTED); + } + + #[test] + fn test_ecc_l8() { + const EXPECTED: [u16; ecc_count(8)] = [538, 446, 840, 510, 163, 708, 177, 666, 423, 600, 707, 913, 770, 571, 156, 683, 676, 697, 898, 776, 128, 851, 163, 854, 135, 661, 880, 279, 92, 324, 397, 207, 379, 223, 574, 9, 70, 858, 878, 579, 61, 551, 261, 388, 315, 856, 266, 865, 923, 38, 313, 62, 381, 198, 265, 256, 385, 878, 347, 532, 821, 53, 855, 225, 697, 826, 263, 334, 207, 565, 460, 496, 705, 599, 383, 289, 178, 168, 401, 268, 555, 190, 922, 284, 180, 810, 891, 832, 636, 813, 894, 495, 701, 484, 204, 793, 129, 164, 444, 228, 636, 98, 809, 57, 736, 697, 727, 534, 889, 480, 898, 773, 234, 851, 880, 843, 714, 443, 412, 489, 578, 468, 367, 663, 11, 686, 319, 352, 345, 670, 106, 106, 219, 466, 439, 350, 538, 66, 852, 175, 465, 731, 332, 110, 926, 491, 18, 422, 736, 797, 624, 376, 728, 526, 735, 200, 502, 923, 789, 529, 923, 706, 384, 869, 172, 548, 520, 463, 813, 384, 793, 231, 190, 653, 864, 351, 400, 525, 487, 828, 654, 307, 141, 638, 770, 775, 282, 54, 758, 197, 492, 320, 86, 790, 275, 237, 923, 25, 591, 605, 61, 824, 79, 631, 532, 337, 867, 423, 340, 597, 682, 923, 287, 408, 503, 361, 881, 196, 468, 759, 746, 389, 124, 784, 198, 865, 538, 451, 178, 772, 653, 121, 497, 598, 711, 716, 241, 159, 429, 88, 799, 761, 639, 105, 54, 807, 351, 435, 793, 873, 360, 8, 881, 479, 693, 576, 849, 875, 771, 621, 134, 863, 8, 171, 799, 924, 103, 63, 491, 538, 597, 855, 697, 499, 7, 886, 286, 85, 107, 220, 319, 124, 197, 150, 729, 899, 585, 540, 676, 414, 256, 856, 596, 259, 882, 436, 26, 273, 753, 127, 679, 390, 654, 42, 276, 420, 247, 629, 116, 803, 131, 25, 403, 645, 462, 897, 151, 622, 108, 167, 227, 831, 887, 662, 739, 263, 829, 56, 624, 317, 908, 378, 39, 393, 861, 338, 202, 179, 907, 109, 360, 736, 554, 342, 594, 125, 433, 394, 195, 698, 844, 912, 530, 842, 337, 294, 528, 231, 735, 93, 8, 579, 42, 148, 609, 233, 782, 887, 888, 915, 620, 78, 137, 161, 282, 217, 775, 564, 33, 195, 36, 584, 679, 775, 476, 309, 230, 303, 708, 143, 679, 502, 814, 193, 508, 532, 542, 580, 603, 641, 338, 361, 542, 537, 810, 394, 764, 136, 167, 611, 881, 775, 267, 433, 142, 202, 828, 363, 101, 728, 660, 583, 483, 786, 717, 190, 809, 422, 567, 741, 695, 310, 120, 177, 47, 494, 345, 508, 16, 639, 402, 625, 286, 298, 358, 54, 705, 916, 291, 424, 375, 883, 655, 675, 498, 498, 884, 862, 365, 310, 805, 763, 855, 354, 777, 543, 53, 773, 120, 408, 234, 728, 438, 914, 3, 670, 546, 465, 449, 923, 51, 546, 709, 648, 96, 320, 682, 326, 848, 234, 855, 791, 20, 97, 901, 351, 317, 764, 767, 312, 206, 139, 610, 578, 646, 264, 389, 238, 675, 595, 430, 88]; + let mut data = [0u16; INPUT_DATA.len() + EXPECTED.len()]; + data[..INPUT_DATA.len()].copy_from_slice(&INPUT_DATA); + generate_ecc(&mut data, 8); + assert_eq!(data[INPUT_DATA.len()..], EXPECTED); + } +} diff --git a/vendor/pdf417/src/high_level.rs b/vendor/pdf417/src/high_level.rs new file mode 100644 index 0000000..5930057 --- /dev/null +++ b/vendor/pdf417/src/high_level.rs @@ -0,0 +1,625 @@ +//! User data to high level encoding conversion functions + +use crate::{ecc, find_variant}; + +use awint_core::{InlAwi, Bits}; +type U160 = InlAwi<160, { Bits::unstable_raw_digits(160) }>; + +/// Codeword used to latch to text mode +pub const M_LATCH_TEXT: u16 = 900; +/// Codeword used to latch to byte mode (if length is multiple of 6 use +/// M_LATCH_BYTE_M6). +pub const M_LATCH_BYTE: u16 = 901; +/// Codeword used to latch to numeric mode +pub const M_LATCH_NUMERIC: u16 = 902; + +// 903 to 912: reserved for future use + +/// Codeword used to switch to byte mode for next codedword (usable only if +/// the current mode is text). +pub const M_SHIFT_BYTE: u16 = 913; + +// 914 to 920: reserved for future use +// 921: used for reader initialization or programming (barcode used to +// enable/disable specific features of the reader). +// 922 to 923: Macro PDF4l7 + +/// Codeword used to latch to byte mode (if length is multiple of 6 use +/// M_LATCH_BYTE_M6). +pub const M_LATCH_BYTE_M6: u16 = 924; +/// Codeword used to specifiy a ECI (user) custom ID +pub const ECI_CUSTOM_ID: u16 = 925; +/// Codeword used to specifiy a ECI code +pub const ECI_GENERAL_ID: u16 = 926; +/// Codeword used to specifiy a ECI code page +pub const ECI_CODE_PAGE: u16 = 927; +// 928: Block start for PDF macro + +/// Codeword used as padding at the end of the data section +pub const CW_PADDING: u16 = M_LATCH_TEXT; + +const MIXED_CHAR_SET: [u8; 15] = [ + b'&', b'\r', b'\t', b',', b':', b'#', b'-', b'.', b'$', b'/', b'+', b'%', b'*', b'=', b'^' +]; +const PUNC_CHAR_SET: [u8; 29] = [ + b';', b'<', b'>', b'@', b'[', b'\\', b']', b'_', b'`', b'~', b'!', b'\r', b'\t', + b',', b':', b'\n', b'-', b'.', b'$', b'/', b'"', b'|', b'*', b'(', b')', b'?', + b'{', b'}', b'\'' +]; + +macro_rules! push { + ($cws:ident, $i:ident, $rh:ident, $($cw:expr),+; $post:ident = $new:expr) => {{ + push!($cws, $i, $rh, $($cw),+); + $post = $new; + }}; + ($cws:ident, $i:ident, $rh:ident, $head:expr, $($cw:expr),+) => { + push!($cws, $i, $rh, $head); + push!($cws, $i, $rh, $($cw),+); + }; + ($cws:ident, $i:ident, $rh:ident, $cw:expr) => {{ + let cw = $cw as u16; + if $rh { + $cws[$i] = $cws[$i] * 30 + cw; + $rh = false; + $i += 1; + } else { + $cws[$i] = cw; + $rh = true; + } + }}; +} + +macro_rules! push_sp { + ($cws:ident, $i:ident, $rh:ident, $cw:expr; $post:ident = $new:expr) => {{ + let cw = $cw as u16; + if $rh { + $cws[$i] = $cws[$i] * 30 + 29; + $rh = false; + $i += 1; + } + + $cws[$i] = cw; + $i += 1; + $post = $new; + }}; +} + +/// Use a PDF417Encoder to encode your data segements to a slice of codewords +/// ready to be rendered. +#[derive(Debug)] +pub struct PDF417Encoder<'a> { + storage: &'a mut [u16], + used: usize, + micro: bool, + // 0: Upper, 1: Lower, 2: Mixed, 3: Punc, 4: Numeric, 5: Byte + last_mode: u8 +} + +impl<'a> PDF417Encoder<'a> { + /// Create a PDF417Encoder with sufficient storage for data segments. Set + /// `micro` to true to encode according to the MicroPDF417 specification. + /// **Note**: You can not mistmatch specifications, if you encode your + /// data according to the classic PDF417, you can not use it to generate + /// a MicroPDF417 and vice-versa. + pub fn new(storage: &'a mut [u16], micro: bool) -> Self { + assert!(storage.len() > 0, "storage must be able to contain at least one codeword"); + if micro { + // Default mode is byte compactation + Self { storage, used: 0, micro, last_mode: 5 } + } else { + // Skip the first codeword (used for length). + Self { storage, used: 1, micro, last_mode: 0 } + } + } + + /// Returns the number of codewords already used + pub fn count(&self) -> usize { + self.used + } + + /// Returns the number of available codewords (excluding required ECC codewords). + pub fn capacity(&self) -> usize { + self.storage.len() + } + + /// Returns the number of free codeword slots including slots filled up by + /// ECC codewords. Be careful, when generating a MicroPDF417 the `val` + /// parameter represents the variant, otherwise the `val` parameter + /// represents the ECC level. + pub fn available(&self, val: u8) -> usize { + let ecc_count = if self.micro { + let variant = val as usize; + assert!(variant <= 34, "invalid variant (0-34)"); + use crate::tables::*; + M_PDF417_VARIANTS[2 * M_PDF417_VARIANTS_COUNT + variant] as usize + } else { + let level = val; + ecc::ecc_count(level) + }; + self.storage.len() - ecc_count - self.used + } + + /// Appends a numeric segment containing a 64-bit unsigned integer `n`. For + /// larger numbers please use the [PDF417Encoder::append_ascii] method which + /// can handle 44+ digit numbers. + pub fn append_num(mut self, mut n: u64) -> Self { + if self.last_mode != 4 { + self.storage[self.used] = M_LATCH_NUMERIC; + self.last_mode = 4; + self.used += 1; + } + + let mut digits = 0; + + // Append a leading 1 to the number to do the base 900 + // conversion. We need to calculate and add 10^(digits). + // Power of 10 (see https://stackoverflow.com/a/44103598) + { + let mut val = n; + let mut p1 = 1; + + while val > 0 { + p1 += p1 << 2; // *5 + val /= 10; + digits += 1; + } + p1 <<= digits; + n += p1; + } + + let nb = digits / 3 + 1; + let mut count = 0; + + while n > 0 { + let (q, r) = (n / 900, n % 900); + n = q; + self.storage[self.used + nb - count - 1] = r as u16; + count += 1; + } + + self.used += nb; + self + } + + /// Appends a bytes segment. + pub fn append_bytes(mut self, bytes: &[u8]) -> Self { + let mut i = self.used; + let mut k = 0; + + if bytes.len() > 1 { + // even if we are in byte mode, it is safer to always emit a LATCH_BYTE + self.storage[i] = if bytes.len() % 6 == 0 { M_LATCH_BYTE_M6 } else { M_LATCH_BYTE }; + self.last_mode = 5; + i += 1; + + while bytes.len()-k >= 6 { + // pack six bytes + let mut s: u64 = 0; + for n in 0..6 { + s = (s << 8) + bytes[k + n] as u64; + } + // append five codewords + for n in 0..5 { + let (q, r) = (s / 900, s % 900); + self.storage[i + 4 - n] = r as u16; + s = q; + } + + i += 5; + k += 6; + } + } else { + if self.last_mode < 4 { // if in text mode + self.storage[i] = M_SHIFT_BYTE; + } else { + self.storage[i] = M_LATCH_BYTE; + self.last_mode = 5; + } + i += 1; + } + + // remaining + while k < bytes.len() { + self.storage[i] = bytes[k] as u16; + k += 1; + i += 1; + } + + self.used = i; + self + } + + /// Appends an ASCII (text) segment. *Warning*: This function uses the + /// PDF417 table based encoding to optimize the size of the text and + /// therefore support only a small set of displayable characters. If you + /// want to encode an UTF-8 string, use [PDF417Encoder::append_utf8] instead + /// (uses more space). + pub fn append_ascii(mut self, s: &str) -> Self { + debug_assert!(s.is_ascii(), "use append_utf8 for UTF-8 strings"); + let out = &mut self.storage; + let s = s.as_bytes(); + + let mut mode: u8 = self.last_mode; // 0: Upper, 1: Lower, 2: Mixed, 3: Punc, 4: Numeric, 5: Byte + let mut i = self.used; + let mut k = 0; + let mut right = false; // false = upper 8 bits | true = lower 8 bits + + if mode == 4 || mode == 5 { + out[i] = M_LATCH_TEXT; + i += 1; + mode = 0; + } + + while k < s.len() { + let c = s[k]; + match c { + c if c.is_ascii_uppercase() => { // b'A'..=b'Z' + match mode { + 0 => (), + 1 => if k + 1 < s.len() && s[k + 1].is_ascii_lowercase() { + push!(out, i, right, 27); + } else { + push!(out, i, right, 29, 29; mode = 0); + }, + 2 => push!(out, i, right, 28; mode = 0), + 3 => push!(out, i, right, 29; mode = 0), + _ => unreachable!("Unknown mode {mode}"), + } + push!(out, i, right, c - b'A'; k = k + 1); + }, + c if c.is_ascii_lowercase() => { // b'a'..=b'z' + match mode { + 0 | 2 => push!(out, i, right, 27; mode = 1), + 1 => (), + 3 => push!(out, i, right, 29, 27; mode = 1), + _ => unreachable!("Unknown mode {mode}"), + } + push!(out, i, right, c - b'a'; k = k + 1); + }, + c if c.is_ascii_digit() => { // b'0'..=b'9' + let mut end = k + 1; + while end < s.len() && end-k < 44 && s[end].is_ascii_digit() { + end += 1; + } + let digits = end - k; + + if digits <= 13 && mode != 4 { + match mode { + 0 | 1 => push!(out, i, right, 28; mode = 2), + 2 => (), + 3 => push!(out, i, right, 29, 28; mode = 2), + _ => unreachable!("Unknown mode {mode}"), + } + while k < end { + push!(out, i, right, s[k] - b'0'; k = k + 1); + } + } else { + if mode != 4 { push_sp!(out, i, right, M_LATCH_NUMERIC; mode = 4); } + + let mut b = U160::zero(); + { + let mut p0 = U160::zero(); + let mut p1 = U160::zero(); + b.bytes_radix_(None, &s[k..end], 10, &mut p0, &mut p1) + .expect("45 digits base 10 should fit in 160 bits"); + + // Append a leading 1 to the number to do the base 900 + // conversion. We need to calculate and add 10^(digits). + // Power of 10 (see https://stackoverflow.com/a/44103598) + p1.uone_(); + p1.shl_(digits).unwrap(); + for _ in 0..digits { + p0.copy_(&p1).unwrap(); + p0.shl_(2).unwrap(); + p1.add_(&p0).unwrap(); + } + b.add_(&p1).unwrap(); + } + let nb = digits / 3 + 1; + let mut count = 0; + + while !b.is_zero() { + let r = b.digit_udivide_inplace_(900).expect("900 > 0"); + out[i + nb - count - 1] = r as u16; + count += 1; + } + + k = end; + i += nb; + } + + if mode == 4 && k < s.len() && !s[k].is_ascii_digit() { + push_sp!(out, i, right, M_LATCH_TEXT; mode = 0); + } + }, + b' ' => { + if mode == 3 { push!(out, i, right, 29; mode = 0) }; + push!(out, i, right, 26; k = k + 1); + }, + c => { + if let Some(p) = MIXED_CHAR_SET.iter().position(|&r| r == c) { + match mode { + 0 | 1 => push!(out, i, right, 28; mode = 2), + 2 => (), + /* no switch if the char is also present in the punc table */ + 3 if (1..=4).contains(&p) || (6..=9).contains(&p) => (), + 3 => push!(out, i, right, 29, 28; mode = 2), + _ => unreachable!("Unknown mode {mode}"), + } + push!(out, i, right, p + 10); + } else if let Some(p) = PUNC_CHAR_SET.iter().position(|&r| r == c) { + if mode != 3 { + let mut end = k + 1; + while end < s.len() && end-k < 3 && PUNC_CHAR_SET.contains(&s[end]) { + end += 1; + } + if end-k >= 3 { // latch + if mode != 2 { push!(out, i, right, 28); } + push!(out, i, right, 25; mode = 3); + } else { // shift + push!(out, i, right, 29); + } + } + push!(out, i, right, p); + } else { // switch to byte mode + if right { + out[i] = out[i] * 30 + 29; + i += 1; + } + // TODO: Encode multiple bytes if consecutive instead of one by one + out[i] = M_SHIFT_BYTE; + out[i + 1] = c as u16; + i += 2; + } + k += 1; + }, + }; + } + + if right { + out[i] = out[i] * 30 + 29; + i += 1; + } + self.used = i; + self.last_mode = mode; + + self + } + + /// Appends a special segement crafted to store an __UTF-8__ string `s`. + /// __Note that the conversion is space inefficient, if the string is + /// composed of ASCII characters, please consider using + /// [PDF417Encoder::append_ascii] instead.__ Internally, we use a ECI + /// identifier (\\000026) to switch to the UTF-8 code page then append a + /// byte segment to store the string's bytes. + pub fn append_utf8(mut self, s: &str) -> Self { + self.storage[self.used] = ECI_CODE_PAGE; // ECI identifier for code page + self.storage[self.used + 1] = 26; // UTF-8 is \000026 + self.used += 2; + + self.append_bytes(s.as_bytes()) + } + + /// Append a single codeword. + pub fn append_codeword(mut self, codeword: u16) -> Self { + self.storage[self.used] = codeword; + self.used += 1; + self + } + + /// Append a slice of codewords. + pub fn append_raw(mut self, codewords: &[u16]) -> Self { + self.storage[self.used..self.used+codewords.len()].copy_from_slice(codewords); + self.used += codewords.len(); + self + } + + /// Call this function to seal your data segments into a slice of codewords + /// ready to be rendered to a PDF417. Both padding and ECC codewords are + /// generated by this function. Be careful, when generating a MicroPDF417 + /// the `val` parameter represents the variant, otherwise the `val` + /// parameter represents the ECC level. + pub fn seal(self, val: u8) -> &'a mut [u16] { + if self.micro { + let variant = val as usize; + assert!(variant <= 34, "invalid variant (0-34)"); + + use crate::tables::*; + let (count, offset) = ( + M_PDF417_VARIANTS[2 * M_PDF417_VARIANTS_COUNT + variant] as usize, + M_PDF417_VARIANTS[3 * M_PDF417_VARIANTS_COUNT + variant] as usize + ); + + let total = self.capacity() - count; + if self.used < total { + self.storage[self.used..total].fill(CW_PADDING); + } + + ecc::generate_micro_ecc(self.storage, count, offset); + } else { + let level = val; + let total = self.capacity() - ecc::ecc_count(level); + self.storage[0] = total as u16; + if self.used < total { + self.storage[self.used..total].fill(CW_PADDING); + } + + ecc::generate_ecc(self.storage, level); + } + + self.storage + } + + /// Automatically try to fit the maximum number of ECC codewords depending + /// on the remaining codeword slots. Be careful, when generating a + /// MicroPDF417, the returned byte represents a variant number (0-34), + /// otherwise its returns the ECC level (0-8). If there is not enough + /// space for the minimum amount of ECC codewords or a big-enough variant + /// does not exist, None is returned. + pub fn fit_ecc(&self) -> Option { + if self.micro { + find_variant(self.used) + } else { + let remaining = self.capacity() - self.used; + let mut level = 9; + while level > 0 && ecc::ecc_count(level - 1) > remaining { + level -= 1; + } + if level == 0 { + None + } else { + Some(level - 1) + } + } + } + + /// Call this function to seal your data segments into a slice of codewords + /// ready to be rendered to a PDF417. Both padding and ECC codewords are + /// generated by this function. This internally uses + /// [PDF417Encoder::fit_ecc] to fit the maximum number of ECC codewords + /// possible. + pub fn fit_seal(self) -> Option<(u8, &'a mut [u16])> { + let v = self.fit_ecc()?; + Some((v, self.seal(v))) + } +} + +#[cfg(test)] +mod tests { + use super::PDF417Encoder; + + #[test] + fn test_encode_ascii_simple() { + let mut codewords = [0u16; 4]; + let ec = PDF417Encoder::new(&mut codewords, false).append_ascii("Test"); + assert_eq!(ec.used, codewords.len()); + assert_eq!(&codewords, &[0, 19 * 30 + 27, 4 * 30 + 18, 19 * 30 + 29]); + } + + #[test] + fn test_generate_ascii_switch_modes() { + let mut codewords = [0u16; 9]; + let ec = PDF417Encoder::new(&mut codewords, false).append_ascii("abc1D234\x1B"); + assert_eq!(ec.used, codewords.len()); + assert_eq!(&codewords, &[0, 27 * 30 + 0, 1 * 30 + 2, 28 * 30 + 1, 28 * 30 + 3, 28 * 30 + 2, 3 * 30 + 4, 913, 0x1B]); + } + + #[test] + fn test_generate_ascii_numeric() { + let mut codewords = [0u16; 12]; + let ec = PDF417Encoder::new(&mut codewords, false).append_ascii("12345678987654321 num"); + assert_eq!(ec.used, codewords.len()); + assert_eq!(&codewords, &[0, 902, 190, 232, 499, 20, 504, 721, 900, 26 * 30 + 27, 13 * 30 + 20, 12 * 30 + 29]); + } + + #[test] + fn test_generate_ascii_numeric_big() { + let mut codewords = [0u16; 20]; + let ec = PDF417Encoder::new(&mut codewords, false) + // [ p1 ][ p2 ] + .append_ascii("123456789876543211234567898765432112345678987654321"); + assert_eq!(ec.used, codewords.len()); + assert_eq!(&codewords, &[0, 902, 491, 81, 137, 725, 651, 455, 511, 858, 135, 138, 488, 568, 447, 553, 198, /* p2 */ 21, 715, 821]); + } + + #[test] + fn test_encode_num() { + let mut codewords = [0u16; 8]; + let ec = PDF417Encoder::new(&mut codewords, false).append_num(12345678987654321); + assert_eq!(ec.used, codewords.len()); + assert_eq!(&codewords, &[0, 902, 190, 232, 499, 20, 504, 721]); + } + + #[test] + fn test_generate_ascii_with_digits() { + let mut codewords = [0u16; 17]; + let ec = PDF417Encoder::new(&mut codewords, false).append_ascii("encoded 0123456789 as digits"); + assert_eq!(ec.used, codewords.len()); + assert_eq!(&codewords, &[0, 27 * 30 + 4, 13 * 30 + 2, 14 * 30 + 3, 4 * 30 + 3, 26 * 30 + 28, 0 * 30 + 1, 2 * 30 + 3, 4 * 30 + 5, 6 * 30 + 7, 8 * 30 + 9, + 26 * 30 + 27, 0 * 30 + 18, 26 * 30 + 3, 8 * 30 + 6, 8 * 30 + 19, 18 * 30 + 29]); + } + + #[test] + fn test_generate_ascii_punc_mixed() { + let mut codewords = [0u16; 18]; + let ec = PDF417Encoder::new(&mut codewords, false).append_ascii("This! Is a `quote (100%)`."); + assert_eq!(ec.used, codewords.len()); + assert_eq!(&codewords, &[0, 19 * 30 + 27, 7 * 30 + 8, 18 * 30 + 29, 10 * 30 + 26, 27 * 30 + 8, 18 * 30 + 26, 0 * 30 + 26, 29 * 30 + 8, 16 * 30 + 20, 14 * 30 + 19, 4 * 30 + 26, 29 * 30 + 23, 28 * 30 + 1, 0 * 30 + 0, 21 * 30 + 25, 24 * 30 + 8, 17 * 30 + 29]); + } + + #[test] + fn test_encode_bytes_multiple() { + let mut codewords = [0u16; 7]; + let ec = PDF417Encoder::new(&mut codewords, false).append_bytes(b"alcool"); + assert_eq!(ec.used, codewords.len()); + assert_eq!(&codewords, &[0, 924, 163, 238, 432, 766, 244]); + } + + #[test] + fn test_encode_bytes_not_multiple() { + let mut codewords = [0u16; 11]; + let ec = PDF417Encoder::new(&mut codewords, false).append_bytes(b"encode bin"); + assert_eq!(ec.used, codewords.len()); + assert_eq!(&codewords, &[0, 901, 169, 883, 224, 680, 517, 32, 98, 105, 110]); + } + + #[test] + fn test_multiple_segments() { + let mut codewords = [0u16; 1 + 3 + 2 + 10]; + + let ec = PDF417Encoder::new(&mut codewords, false) + .append_ascii("Test") + .append_num(42) + .append_bytes(b"encode bin"); + assert_eq!(ec.used, codewords.len()); + assert_eq!(&codewords, &[ + 0, + 19 * 30 + 27, 4 * 30 + 18, 19 * 30 + 29, + 902, 142, + 901, 169, 883, 224, 680, 517, 32, 98, 105, 110 + ]); + } + + #[test] + fn test_seal_normal() { + let mut codewords = [0u16; 1 + 3 + 3 + 10 + 2]; + + PDF417Encoder::new(&mut codewords, false) + .append_ascii("Test") + .append_num(42) + .append_num(42) + .append_bytes(b"encode bin") + .seal(0); + + assert_eq!(&codewords, &[ + 17, + 19 * 30 + 27, 4 * 30 + 18, 19 * 30 + 29, + 902, 142, 142, + 901, 169, 883, 224, 680, 517, 32, 98, 105, 110, + // ecc + 516, 287 + ]); + } + + #[test] + fn test_seal_micro() { + use crate::tables::{M_PDF417_VARIANTS, M_PDF417_VARIANTS_COUNT}; + let mut codewords = [0u16; 1 + 3 + 3 + 10 + M_PDF417_VARIANTS[2 * M_PDF417_VARIANTS_COUNT + 0] as usize]; + + PDF417Encoder::new(&mut codewords, true) + .append_ascii("Test") + .append_num(42) + .append_num(42) + .append_bytes(b"encode bin") + .seal(0); + + assert_eq!(&codewords, &[ + 900, + 19 * 30 + 27, 4 * 30 + 18, 19 * 30 + 29, + 902, 142, 142, + 901, 169, 883, 224, 680, 517, 32, 98, 105, 110, + // ecc + 383, 745, 811, 163, 659, 400, 129 + ]); + } +} diff --git a/vendor/pdf417/src/lib.rs b/vendor/pdf417/src/lib.rs new file mode 100644 index 0000000..09b8130 --- /dev/null +++ b/vendor/pdf417/src/lib.rs @@ -0,0 +1,700 @@ +//! # PDF417 Generator +//! +//! A no-std and no-alloc PDF417 encoder for embedded applications (also works for +//! std). This library implements mutliple encoding modes for numbers, strings and +//! bytes according to the specification. You can also customize the rendering of +//! the barcodes (size, storage and inverted) and supports both Truncated PDF417 +//! and MicroPDF417. +//! +//! #### Basic Example +//! ``` +//! # use pdf417::*; +//! const COLS: u8 = 3; +//! const ROWS: u8 = 5; +//! const WIDTH: usize = pdf417_width!(COLS); +//! const HEIGHT: usize = pdf417_height!(ROWS); +//! +//! // High-level encoding +//! let mut input = [0u16; (ROWS * COLS) as usize]; +//! let (level, _) = PDF417Encoder::new(&mut input, false) +//! .append_ascii("Hello, world!").fit_seal().unwrap(); +//! +//! // Rendering +//! let mut storage = [false; WIDTH * HEIGHT]; +//! PDF417::new(&input, ROWS, COLS, level).render(&mut storage[..]); +//! ``` +//! +//! ### Data Segments +//! +//! You can multiple data segments (aka encoding modes) on a single barcode. The +//! available types are: +//! - **numeric**: efficient encoding of 44+ digit numbers +//! - **ascii**: efficient encoding of text (alphanumeric + punctuation) with +//! support for non-displyable ASCII values which are encoded as raw bytes. +//! - **bytes**: binary data as bytes +//! +//! An additional **UTF-8** mode is available which allows encoding of UTF-8 strings +//! using an ECI identifier and byte encoding mode (note that this encoding takes +//! significantly more space than the ASCII mode). +//! +//! > See the different methods available on [PDF417Encoder] struct. +//! +//! ### MicroPDF417 +//! +//! This library also supports the generation of MicroPDF417. Here is an +//! example: +//! +//! ``` +//! # use pdf417::*; +//! const COLS: u8 = 1; +//! const ROWS: u8 = 11; +//! const WIDTH: usize = m_pdf417_width!(COLS); +//! const HEIGHT: usize = m_pdf417_height!(ROWS); +//! +//! // High-level encoding +//! let variant = get_variant(ROWS, COLS).unwrap(); +//! let mut input = [0u16; (ROWS * COLS) as usize]; +//! PDF417Encoder::new(&mut input, true) +//! .append_num(12345678).seal(variant); +//! +//! // Rendering +//! let mut storage = [false; WIDTH * HEIGHT]; +//! MicroPDF417::new(&input, variant).render(&mut storage[..]); +//! ``` +//! +//! Do not forget to set the `micro` parameter to true in [PDF417Encoder::new]. + +#![no_std] +#![warn(missing_docs)] + +mod tables; +use tables::{HL_TO_LL, M_PDF417_VARIANTS, M_PDF417_VARIANTS_COUNT, M_PDF417_RAP, M_PDF417_SIDE, M_PDF417_CENTER}; + +pub mod ecc; +pub mod high_level; +pub use high_level::*; +pub use tables::{get_variant, find_variant, variant_dim}; + +const START: u32 = 0b11111111010101000; +const END: u32 = 0b111111101000101001; + +/// Size in pixels of the PDF417 start pattern +pub const START_PATTERN_LEN: u8 = 17; +/// Size in pixels of the PDF417 end pattern +pub const END_PATTERN_LEN: u8 = 18; + +/// Minimum number of rows in a PDF417 barcode. +pub const MIN_ROWS: u8 = 3; +/// Maximum number of rows in a PDF417 barcode. +pub const MAX_ROWS: u8 = 90; +/// Minimum number of data columns in a PDF417 barcode. +pub const MIN_COLS: u8 = 1; +/// Maximum number of data columns in a PDF417 barcode. +pub const MAX_COLS: u8 = 30; + +/// (rows, cols, (scaleX, scaleY), inverted) +pub type PDF417Config = (u8, u8, (u32, u32), bool); + +/// A receiver for rendering PDF417 barcodes. +pub trait RenderTarget { + /// User defined type for storing the progress of the rendering and/or + /// various configuration values. + type State; + + /// Called at the beginning of the rendering of an PDF417 passed by + /// reference. You can store any state you want which you will be able to + /// use later in the next functions. + fn begin(&self, config: PDF417Config) -> Self::State; + + /// Called at the beginning of a row (before the start pattern and left + /// codeword are appended). + fn row_start(&mut self, state: &mut Self::State); + + /// Called at the end of a row (after the right codeword and end + /// pattern are appended). + fn row_end(&mut self, state: &mut Self::State); + + /// Append the `count` least significant bits stored in `value` as pixels. + /// The `count` is guaranteed to be less or equal than 32. A set bit + /// represents a black pixel and a unset bit a white pixel. + fn append_bits(&mut self, state: &mut Self::State, value: u32, count: u8); + + #[allow(unused_variables)] + /// Called at the end of the rendering of an PDF417. The last state is + /// moved into this function. + fn end(&mut self, state: Self::State) {} +} + +#[derive(Debug, Default)] +/// Struct used to implement RenderTarget for \[bool\] +/// +/// This allows passing an \[bool\] to [PDF417::render]. +pub struct BoolSliceRenderConfig { + i: usize, + row_start: usize, + scale: (u32, u32), + inverted: bool +} + +impl RenderTarget for [bool] { + type State = BoolSliceRenderConfig; + + fn begin(&self, (_, _, scale, inverted): PDF417Config) -> Self::State { + BoolSliceRenderConfig { scale, inverted, ..Default::default() } + } + + fn row_start(&mut self, state: &mut Self::State) { + state.row_start = state.i; + } + + fn row_end(&mut self, state: &mut Self::State) { + let w = state.scale.1 as usize; + if w > 1 { + let mut i = state.i; + let len = state.i - state.row_start; + for _ in 0..(w - 1) { + self.copy_within((state.row_start)..(state.row_start+len), i); + i += len; + } + state.i = i; + } + } + + fn append_bits(&mut self, state: &mut Self::State, mut value: u32, count: u8) { + if state.inverted { + value = !value; + } + let w = state.scale.0 as usize; + let i = &mut state.i; + let mut mask = 1 << (count as u32 - 1); + for _ in 0..count { + self[(*i)..(*i + w)].fill((value & mask) != 0); + *i += w; + mask >>= 1; + } + } +} + +#[derive(Debug, Default)] +struct BitShifter { + cursor: usize, + bit: u8 +} + +impl BitShifter { + #[inline(always)] + pub fn shift(&mut self, storage: &mut [u8], v: bool) { + if v { storage[self.cursor] |= 1 << (7 - self.bit); } + self.bit += 1; + if self.bit == 8 { + self.cursor += 1; + self.bit = 0; + } + } + + #[inline(always)] + pub fn skip(&mut self) { + self.cursor += 1; + self.bit = 0; + } + + #[inline(always)] + pub fn move_to(&mut self, cursor: usize) { + self.cursor = cursor; + self.bit = 0; + } +} + +#[derive(Debug, Default)] +/// Struct used to implement RenderTarget for \[u8\]. +/// +/// This allows passing an \[u8\] to [PDF417::render]. Please note that +/// after each row there is some padding zeros at the end of the current byte. +/// Therefore, when using the slice of bytes to renders you must skip these +/// bytes by checking if we reached the end of the row and discarding the end of +/// the byte being rendered. +pub struct ByteSliceRenderConfig { + bs: BitShifter, + row_start: usize, + inverted: bool, + scale: (u32, u32) +} + +impl RenderTarget for [u8] { + type State = ByteSliceRenderConfig; + + fn begin(&self, (_, _, scale, inverted): PDF417Config) -> Self::State { + ByteSliceRenderConfig { scale, inverted, ..Default::default() } + } + + fn row_start(&mut self, state: &mut Self::State) { + state.row_start = state.bs.cursor; + } + + fn row_end(&mut self, state: &mut Self::State) { + if state.bs.bit > 0 { + // add padding to the last byte of the row + state.bs.skip(); + } + + let h = state.scale.1; + if h > 1 { + let mut i = state.bs.cursor; + let j = state.row_start; + let len = i - j; + for _ in 0..(h - 1) { + self.copy_within(j..(j+len), i); + i += len; + } + state.bs.move_to(i); + } + } + + fn append_bits(&mut self, state: &mut Self::State, mut value: u32, mut count: u8) { + if state.inverted { + value = !value; + } + let w = state.scale.0 as usize; + while count > 0 { + count -= 1; + let is_set = (value >> count) & 1 == 1; + for _ in 0..w { + state.bs.shift(self, is_set); + } + } + } +} + +const LEADING_ONE: u32 = 1 << 16; +macro_rules! cw { + ($tb:ident, $val:expr) => { + LEADING_ONE + HL_TO_LL[$tb * 929 + $val as usize] as u32 + } +} + +#[macro_export] +/// Calculate the width in pixels of a PDF417 barcode according to the +/// configuration (Columns, X scale, Is Truncated). Only the number of columns +/// is required, other parameters can be omitted in order. +macro_rules! pdf417_width { + ($cols:expr) => { + pdf417_width!($cols, 1); + }; + ($cols:expr, $scale_x:expr) => { + pdf417_width!($cols, $scale_x, false); + }; + ($cols:expr, $scale_x:expr, $truncated:expr) => { + if $truncated { + (pdf417::START_PATTERN_LEN as usize + 17 + $cols as usize * 17 + 1) + * $scale_x as usize + } else { + (pdf417::START_PATTERN_LEN as usize + 17 + $cols as usize * 17 + 17 + pdf417::END_PATTERN_LEN as usize) + * $scale_x as usize + } + }; +} + +#[macro_export] +/// Calculate the height in pixels of a PDF417 barcode according to the +/// configuration (Rows, Y scale). Only the number of rows is required, other +/// parameters can be omitted in order. +macro_rules! pdf417_height { + ($rows:expr) => { + pdf417_height!($rows, 1); + }; + ($rows:expr, $scale_y:expr) => { + $rows as usize * $scale_y as usize + }; +} + +#[derive(Debug, Clone)] +/// Configuration and Rendering of PDF417 barcodes. +pub struct PDF417<'a> { + codewords: &'a [u16], + rows: u8, + cols: u8, + level: u8, + scale: (u32, u32), + truncated: bool, + inverted: bool +} + +impl<'a> PDF417<'a> { + /// Creates a new PDF417 with the user's data section (codewords slice), + /// the level of error correction and the layout configuration + /// (rows and cols). The total codewords capacity is calculated with + /// rows \* cols and must be greater or equal to the number of codewords + /// in the `codewords` slice. Please make sure your codewords + /// slice is valid, you can use [PDF417Encoder] to fill it accordingly. + pub const fn new(codewords: &'a [u16], rows: u8, cols: u8, level: u8) -> Self { + assert!(rows >= MIN_ROWS && rows <= MAX_ROWS, "The number of rows must be between 3 and 90"); + assert!(cols >= MIN_COLS && cols <= MAX_COLS, "The number of columns must be between 1 and 30"); + assert!(codewords.len() == (rows as usize * cols as usize), + "The data will not fit in the provided configuration"); + assert!(level < 9, "ECC level must be between 0 and 8 inclusive"); + + PDF417 { codewords, rows, cols, level, scale: (1, 1), truncated: false, inverted: false } + } + + /// Returns if the PDF417 is set to be rendered as a Truncated PDF417. + /// + /// See also [set_truncated](PDF417::set_truncated). + pub const fn is_truncated(&self) -> bool { + self.truncated + } + + /// Marks whether this PDF417 should be rendered as a Truncated PDF417. + /// + /// See also [set_truncated](PDF417::set_truncated). + pub const fn truncated(mut self, truncated: bool) -> Self { + self.truncated = truncated; + self + } + + /// Marks whether this PDF417 should be rendered as a Truncated PDF417. + /// Quote from the PDF417 specification: + /// > In a relatively "clean" environment where label damage is unlikely + /// > (e.g., an office), the right row indicators can be omitted and the stop + /// > pattern can be reduced to one module width bar. + /// > This truncation reduces the non-data overhead from 4 codewords per row + /// > to 2 codewords per row, with a trade-off in decode performance and + /// > robustness, or the ability to withstand degradation. + pub const fn set_truncated(&mut self, truncated: bool) -> &mut Self { + self.truncated = truncated; + self + } + + /// Returns the scale of the PDF417 as (Scale X axis, Scale Y axis). + pub const fn scale(&self) -> (u32, u32) { + self.scale + } + + /// Sets the scale of the PDF417 on both axis. The scale tuple stores + /// the non-zero scale values as (Scale X axis, Scale Y axis). + pub const fn scaled(mut self, scale: (u32, u32)) -> Self { + assert!(scale.0 != 0 && scale.1 != 0, "scale cannot be zero"); + self.scale = scale; + self + } + + /// Sets the scale of the PDF417 on both axis. See also [scaled](PDF417::scaled). + pub const fn set_scaled(&mut self, scale: (u32, u32)) -> &mut Self { + assert!(scale.0 != 0 && scale.1 != 0, "scale cannot be zero"); + self.scale = scale; + self + } + + /// Returns if the PDF417 is set to be rendered with inverted colors. + pub const fn is_inverted(&self) -> bool { + self.inverted + } + + /// Marks whether this PDF417 should be rendered with pixel values inverted. + pub const fn inverted(mut self, inverted: bool) -> Self { + self.inverted = inverted; + self + } + + /// Marks whether this PDF417 should be rendered with pixel values inverted. + pub const fn set_inverted(&mut self, inverted: bool) -> &mut Self { + self.inverted = inverted; + self + } + + /// Get the number of rows of the PDF417. + pub const fn rows(&self) -> u8 { + self.rows + } + + /// Get the number of columns of the PDF417. This is used to lay down the + /// start, left, right and end indicators in the render function. + pub const fn cols(&self) -> u8 { + self.cols + } + + /// Set the dimensions of the PDF417 as (number of rows, number of cols). + pub const fn with_dimensions(mut self, (rows, cols): (u8, u8)) -> Self { + assert!(rows >= MIN_ROWS && rows <= MAX_ROWS, "The number of rows must be between 3 and 90"); + assert!(cols >= MIN_COLS && cols <= MAX_COLS, "The number of columns must be between 1 and 30"); + assert!(self.codewords.len() == (rows as usize * cols as usize), + "The data will not fit in the provided configuration"); + self.rows = rows; + self.cols = cols; + self + } + + /// Set the number of columns of the PDF417. + pub const fn set_dimensions(&mut self, (rows, cols): (u8, u8)) -> &mut Self { + assert!(rows >= MIN_ROWS && rows <= MAX_ROWS, "The number of rows must be between 3 and 90"); + assert!(cols >= MIN_COLS && cols <= MAX_COLS, "The number of columns must be between 1 and 30"); + assert!(self.codewords.len() == (rows as usize * cols as usize), + "The data will not fit in the provided configuration"); + self.rows = rows; + self.cols = cols; + self + } + + /// Render the PDF417 to a suitable render target. The scale, truncated, + /// cols, and rows configuration values are used here to lay down the + /// pixels to construct a valid barcode according to the specification. + /// + /// **Note**: Both scale and inverted parameters are handled by the + /// RenderTarget which allows for specialized (and faster) ways of copying + /// or updating pixel values. + pub fn render(&self, storage: &mut Target) { + let (rows, cols) = (self.rows as u32, self.cols as u32); + let rows_val = (rows - 1) / 3; + let cols_val = cols - 1; + let level_val = self.level as u32 * 3 + (rows - 1) % 3; + + let mut table = 0; + let mut state = storage.begin((self.rows, self.cols, self.scale, self.inverted)); + + for row in 0..rows { + storage.row_start(&mut state); + + // row start pattern + storage.append_bits(&mut state, START, START_PATTERN_LEN); + + // row left pattern + let cw = (row / 3) * 30 + match table { + 0 => rows_val, + 1 => level_val, + 2 => cols_val, + _ => unreachable!() + }; + storage.append_bits(&mut state, cw!(table, cw), 17); + + for col in 0..cols { + let cw = self.codewords[(row * cols + col) as usize]; + storage.append_bits(&mut state, cw!(table, cw), 17); + } + + if self.truncated { + // stop pattern reduced to one module width bar + storage.append_bits(&mut state, 1, 1); + } else { + // row right codeword + let cw = (row / 3) * 30 + match table { + 0 => cols_val, + 1 => rows_val, + 2 => level_val, + _ => unreachable!() + }; + storage.append_bits(&mut state, cw!(table, cw), 17); + + storage.append_bits(&mut state, END, END_PATTERN_LEN); + } + + storage.row_end(&mut state); + + if table == 2 { table = 0; } else { table += 1 }; + } + + storage.end(state); + } +} + +#[macro_export] +/// Calculate the width in pixels of a MicroPDF417 barcode according to the +/// configuration (Columns, X scale). Only the number of columns is required, +/// other parameters can be omitted in order. +macro_rules! m_pdf417_width { + ($cols:expr) => { + m_pdf417_width!($cols, 1); + }; + ($cols:expr, $scale_x:expr) => { + (10 + $cols as usize * 17 + ($cols as usize / 3) * 10 + 10 + 1) + * $scale_x as usize + }; +} + +#[macro_export] +/// Calculate the height in pixels of a MicroPDF417 barcode according to the +/// configuration (Rows, Y scale). Only the number of rows is required, other +/// parameters can be omitted in order. Note that the default Y scale is 2. +macro_rules! m_pdf417_height { + ($rows:expr) => { + m_pdf417_height!($rows, 2); + }; + ($rows:expr, $scale_y:expr) => { + $rows as usize * $scale_y as usize + }; +} + +#[derive(Debug, Clone)] +/// Configuration and Rendering of MicroPDF417 barcodes. +pub struct MicroPDF417<'a> { + codewords: &'a [u16], + variant: u8, + scale: (u32, u32), + inverted: bool +} + +impl<'a> MicroPDF417<'a> { + /// Creates a new MicroPDF417 with the user's data section (codewords slice) + /// and the layout configuration (variant). See [get_variant], [find_variant] + /// and [variant_dim] for more information. Please make sure your codewords + /// slice is valid, you can use [PDF417Encoder] to fill it accordingly. + pub const fn new(codewords: &'a [u16], variant: u8) -> Self { + assert!(variant <= 34); + MicroPDF417 { codewords, variant, scale: (1, 2), inverted: false } + } + + /// Returns the scale of the MicroPDF417 as (Scale X axis, Scale Y axis). + pub const fn scale(&self) -> (u32, u32) { + self.scale + } + + /// Sets the scale of the MicroPDF417 on both axis. The scale tuple stores + /// the non-zero scale values as (Scale X axis, Scale Y axis). + pub const fn scaled(mut self, scale: (u32, u32)) -> Self { + assert!(scale.0 != 0 && scale.1 != 0, "scale cannot be zero"); + self.scale = scale; + self + } + + /// Sets the scale of the MicroPDF417 on both axis. See also [scaled](MicroPDF417::scaled). + pub const fn set_scaled(&mut self, scale: (u32, u32)) -> &mut Self { + assert!(scale.0 != 0 && scale.1 != 0, "scale cannot be zero"); + self.scale = scale; + self + } + + /// Returns if the MicroPDF417 is set to be rendered with inverted colors. + pub const fn is_inverted(&self) -> bool { + self.inverted + } + + /// Marks whether this MicroPDF417 should be rendered with pixel values inverted. + pub const fn inverted(mut self, inverted: bool) -> Self { + self.inverted = inverted; + self + } + + /// Marks whether this MicroPDF417 should be rendered with pixel values inverted. + pub const fn set_inverted(&mut self, inverted: bool) -> &mut Self { + self.inverted = inverted; + self + } + + /// Get the number of rows of the MicroPDF417. + pub const fn rows(&self) -> u8 { + M_PDF417_VARIANTS[1 * M_PDF417_VARIANTS_COUNT + self.variant as usize] as u8 + } + + /// Get the number of columns of the MicroPDF417. This is used to lay down + /// the left, center and right indicators in the render function. + pub const fn cols(&self) -> u8 { + M_PDF417_VARIANTS[0 * M_PDF417_VARIANTS_COUNT + self.variant as usize] as u8 + } + + /// Get the variant number of this MicroPDF417. + pub const fn variant(&self) -> u8 { + self.variant + } + + /// Render the MicroPDF417 to a suitable render target. The scale, variant + /// number values are used here to lay down the pixels to construct a valid + /// barcode according to the specification. + /// + /// **Note**: Both scale and inverted parameters are handled by the + /// RenderTarget which allows for specialized (and faster) ways of copying + /// or updating pixel values. + pub fn render(&self, storage: &mut Target) { + let variant = self.variant as usize; + let (cols, rows, mut left, mut center, mut right, mut table) = ( + self.cols() as usize, + self.rows() as usize, + M_PDF417_RAP[0 * M_PDF417_VARIANTS_COUNT + variant] as usize - 1, + M_PDF417_RAP[1 * M_PDF417_VARIANTS_COUNT + variant] as usize - 1, + M_PDF417_RAP[2 * M_PDF417_VARIANTS_COUNT + variant] as usize - 1, + M_PDF417_RAP[3 * M_PDF417_VARIANTS_COUNT + variant] as usize, + ); + let mut state = storage.begin((rows as u8, cols as u8, self.scale, self.inverted)); + + for row in 0..rows { + storage.row_start(&mut state); + storage.append_bits(&mut state, M_PDF417_SIDE[left] as u32, 10); + + let mut col = 0; + while col < cols && col < 2 { + storage.append_bits(&mut state, cw!(table, self.codewords[row * cols + col]), 17); + col += 1; + } + + if col < cols { + storage.append_bits(&mut state, M_PDF417_CENTER[center] as u32, 10); + + while col < cols { + storage.append_bits(&mut state, cw!(table, self.codewords[row * cols + col]), 17); + col += 1; + } + } + + storage.append_bits(&mut state, ((M_PDF417_SIDE[right] as u32) << 1) | 1, 11); + storage.row_end(&mut state); + + if left == 51 { left = 0; } else { left += 1; } + if center == 51 { center = 0; } else { center += 1; } + if right == 51 { right = 0; } else { right += 1; } + if table == 2 { table = 0; } else { table += 1; } + } + + storage.end(state); + } +} + +#[cfg(test)] +mod tests { + use super::RenderTarget; + + #[test] + fn test_append_bits_to_bool_slice() { + let mut t = [false; 13]; + let mut state = t.begin((3, 1, (1, 1), false)); + t.append_bits(&mut state, 0b110001, 6); + t.append_bits(&mut state, 0b11, 2); + t.append_bits(&mut state, 0b00111, 5); + + assert_eq!(&t, &[true, true, false, false, false, true, true, true, false, false, true, true, true]); + } + + #[test] + fn test_append_bits_to_byte_slice() { + let mut t = [0u8; 5]; + let mut state = t.begin((3, 1, (1, 1), false)); + t.append_bits(&mut state, 0b10101010_10101010_1, 17); + t.append_bits(&mut state, 0b1110001_110001, 13); + t.append_bits(&mut state, 0b11, 2); + t.append_bits(&mut state, 0b0000111, 7); + + assert_eq!(&t, &[0b10101010, 0b10101010, 0b11110001, 0b11000111, 0b00001110]); + } + + #[test] + fn test_append_bits_to_bool_slice_scaled() { + let mut t = [false; 6 * 3 * 2]; + let mut state = t.begin((3, 1, (3, 2), false)); + t.row_start(&mut state); + t.append_bits(&mut state, 0b110001, 6); + + assert_eq!(&t[..(t.len()/2)], &[true, true, true, true, true, true, false, false, false, false, false, false, false, false, false, true, true, true], "Testing X scale"); + + t.row_end(&mut state); + assert_eq!(&t[(t.len()/2)..], &[true, true, true, true, true, true, false, false, false, false, false, false, false, false, false, true, true, true],"Testing Y scale"); + } + + #[test] + fn test_append_bits_to_byte_slice_scaled() { + let mut t = [0u8; (8 * 3 * 2) / 8]; + let mut state = t.begin((3, 1, (3, 2), false)); + t.row_start(&mut state); + t.append_bits(&mut state, 0b01000111, 8); + + assert_eq!(&t[..(t.len()/2)], &[0b00011100, 0b00000001, 0b11111111], "Testing X scale"); + + t.row_end(&mut state); + assert_eq!(&t[(t.len()/2)..], &[0b00011100, 0b00000001, 0b11111111], "Testing Y scale"); + } +} diff --git a/vendor/pdf417/src/tables.rs b/vendor/pdf417/src/tables.rs new file mode 100644 index 0000000..0620d36 --- /dev/null +++ b/vendor/pdf417/src/tables.rs @@ -0,0 +1,3043 @@ +pub const ECC_L0: [u16; 2] = [27, 917]; + +pub const ECC_L1: [u16; 4] = [ + 522, 568, 723, 809 +]; + +pub const ECC_L2: [u16; 8] = [ + 237, 308, 436, 284, 646, 653, 428, 379 +]; + +pub const ECC_L3: [u16; 16] = [ + 274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65 +]; + +pub const ECC_L4: [u16; 32] = [ + 361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517, + 273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410 +]; + +pub const ECC_L5: [u16; 64] = [ + 539, 422, 6, 93, 862, 771, 453, 106, 610, 287, 107, 505, 733, 877, 381, 612, + 723, 476, 462, 172, 430, 609, 858, 822, 543, 376, 511, 400, 672, 762, 283, 184, + 440, 35, 519, 31, 460, 594, 225, 535, 517, 352, 605, 158, 651, 201, 488, 502, + 648, 733, 717, 83, 404, 97, 280, 771, 840, 629, 4, 381, 843, 623, 264, 543, +]; + +pub const ECC_L6: [u16; 128] = [ + 521, 310, 864, 547, 858, 580, 296, 379, 53, 779, 897, 444, 400, 925, 749, 415, + 822, 93, 217, 208, 928, 244, 583, 620, 246, 148, 447, 631, 292, 908, 490, 704, + 516, 258, 457, 907, 594, 723, 674, 292, 272, 96, 684, 432, 686, 606, 860, 569, + 193, 219, 129, 186, 236, 287, 192, 775, 278, 173, 40, 379, 712, 463, 646, 776, + 171, 491, 297, 763, 156, 732, 95, 270, 447, 90, 507, 48, 228, 821, 808, 898, + 784, 663, 627, 378, 382, 262, 380, 602, 754, 336, 89, 614, 87, 432, 670, 616, + 157, 374, 242, 726, 600, 269, 375, 898, 845, 454, 354, 130, 814, 587, 804, 34, + 211, 330, 539, 297, 827, 865, 37, 517, 834, 315, 550, 86, 801, 4, 108, 539, +]; + +pub const ECC_L7: [u16; 256] = [ + 524, 894, 75, 766, 882, 857, 74, 204, 82, 586, 708, 250, 905, 786, 138, 720, + 858, 194, 311, 913, 275, 190, 375, 850, 438, 733, 194, 280, 201, 280, 828, 757, + 710, 814, 919, 89, 68, 569, 11, 204, 796, 605, 540, 913, 801, 700, 799, 137, + 439, 418, 592, 668, 353, 859, 370, 694, 325, 240, 216, 257, 284, 549, 209, 884, + 315, 70, 329, 793, 490, 274, 877, 162, 749, 812, 684, 461, 334, 376, 849, 521, + 307, 291, 803, 712, 19, 358, 399, 908, 103, 511, 51, 8, 517, 225, 289, 470, + 637, 731, 66, 255, 917, 269, 463, 830, 730, 433, 848, 585, 136, 538, 906, 90, + 2, 290, 743, 199, 655, 903, 329, 49, 802, 580, 355, 588, 188, 462, 10, 134, + 628, 320, 479, 130, 739, 71, 263, 318, 374, 601, 192, 605, 142, 673, 687, 234, + 722, 384, 177, 752, 607, 640, 455, 193, 689, 707, 805, 641, 48, 60, 732, 621, + 895, 544, 261, 852, 655, 309, 697, 755, 756, 60, 231, 773, 434, 421, 726, 528, + 503, 118, 49, 795, 32, 144, 500, 238, 836, 394, 280, 566, 319, 9, 647, 550, + 73, 914, 342, 126, 32, 681, 331, 792, 620, 60, 609, 441, 180, 791, 893, 754, + 605, 383, 228, 749, 760, 213, 54, 297, 134, 54, 834, 299, 922, 191, 910, 532, + 609, 829, 189, 20, 167, 29, 872, 449, 83, 402, 41, 656, 505, 579, 481, 173, + 404, 251, 688, 95, 497, 555, 642, 543, 307, 159, 924, 558, 648, 55, 497, 10, +]; + +pub const ECC_L8: [u16; 512] = [ + 352, 77, 373, 504, 35, 599, 428, 207, 409, 574, 118, 498, 285, 380, 350, 492, + 197, 265, 920, 155, 914, 299, 229, 643, 294, 871, 306, 88, 87, 193, 352, 781, + 846, 75, 327, 520, 435, 543, 203, 666, 249, 346, 781, 621, 640, 268, 794, 534, + 539, 781, 408, 390, 644, 102, 476, 499, 290, 632, 545, 37, 858, 916, 552, 41, + 542, 289, 122, 272, 383, 800, 485, 98, 752, 472, 761, 107, 784, 860, 658, 741, + 290, 204, 681, 407, 855, 85, 99, 62, 482, 180, 20, 297, 451, 593, 913, 142, + 808, 684, 287, 536, 561, 76, 653, 899, 729, 567, 744, 390, 513, 192, 516, 258, + 240, 518, 794, 395, 768, 848, 51, 610, 384, 168, 190, 826, 328, 596, 786, 303, + 570, 381, 415, 641, 156, 237, 151, 429, 531, 207, 676, 710, 89, 168, 304, 402, + 40, 708, 575, 162, 864, 229, 65, 861, 841, 512, 164, 477, 221, 92, 358, 785, + 288, 357, 850, 836, 827, 736, 707, 94, 8, 494, 114, 521, 2, 499, 851, 543, + 152, 729, 771, 95, 248, 361, 578, 323, 856, 797, 289, 51, 684, 466, 533, 820, + 669, 45, 902, 452, 167, 342, 244, 173, 35, 463, 651, 51, 699, 591, 452, 578, + 37, 124, 298, 332, 552, 43, 427, 119, 662, 777, 475, 850, 764, 364, 578, 911, + 283, 711, 472, 420, 245, 288, 594, 394, 511, 327, 589, 777, 699, 688, 43, 408, + 842, 383, 721, 521, 560, 644, 714, 559, 62, 145, 873, 663, 713, 159, 672, 729, + 624, 59, 193, 417, 158, 209, 563, 564, 343, 693, 109, 608, 563, 365, 181, 772, + 677, 310, 248, 353, 708, 410, 579, 870, 617, 841, 632, 860, 289, 536, 35, 777, + 618, 586, 424, 833, 77, 597, 346, 269, 757, 632, 695, 751, 331, 247, 184, 45, + 787, 680, 18, 66, 407, 369, 54, 492, 228, 613, 830, 922, 437, 519, 644, 905, + 789, 420, 305, 441, 207, 300, 892, 827, 141, 537, 381, 662, 513, 56, 252, 341, + 242, 797, 838, 837, 720, 224, 307, 631, 61, 87, 560, 310, 756, 665, 397, 808, + 851, 309, 473, 795, 378, 31, 647, 915, 459, 806, 590, 731, 425, 216, 548, 249, + 321, 881, 699, 535, 673, 782, 210, 815, 905, 303, 843, 922, 281, 73, 469, 791, + 660, 162, 498, 308, 155, 422, 907, 817, 187, 62, 16, 425, 535, 336, 286, 437, + 375, 273, 610, 296, 183, 923, 116, 667, 751, 353, 62, 366, 691, 379, 687, 842, + 37, 357, 720, 742, 330, 5, 39, 923, 311, 424, 242, 749, 321, 54, 669, 316, + 342, 299, 534, 105, 667, 488, 640, 672, 576, 540, 316, 486, 721, 610, 46, 656, + 447, 171, 616, 464, 190, 531, 297, 321, 762, 752, 533, 175, 134, 14, 381, 433, + 717, 45, 111, 20, 596, 284, 736, 138, 646, 411, 877, 669, 141, 919, 45, 780, + 407, 164, 332, 899, 165, 726, 600, 325, 498, 655, 357, 752, 768, 223, 849, 647, + 63, 310, 863, 251, 366, 304, 282, 738, 675, 410, 389, 244, 31, 121, 303, 263, +]; + +/// High level encoding to low level encoding +pub const HL_TO_LL: [u16; 929 * 3] = [ + // Table 1 + 0b1101010111000000, + 0b1110101011110000, + 0b1111010101111100, + 0b1101010011100000, + 0b1110101001111000, + 0b1111010100111110, + 0b1010100011000000, + 0b1101010001110000, + 0b1010100001100000, + 0b0101000001000000, + 0b1010100000110000, + 0b0101000000100000, + 0b1010110111000000, + 0b1101011011110000, + 0b1110101101111100, + 0b1010110011100000, + 0b1101011001111000, + 0b1110101100111110, + 0b0101100011000000, + 0b1010110001110000, + 0b0101100001100000, + 0b0101110111000000, + 0b1010111011110000, + 0b1101011101111100, + 0b0101110011100000, + 0b1010111001111000, + 0b1101011100111110, + 0b0101110001110000, + 0b1010111000111100, + 0b0101111011110000, + 0b1010111101111100, + 0b0101111001111000, + 0b1010111100111110, + 0b0101111101111100, + 0b1111010111111010, + 0b1101001011100000, + 0b1110100101111000, + 0b1111010010111110, + 0b1010010011000000, + 0b1101001001110000, + 0b1110100100111100, + 0b1010010001100000, + 0b1101001000111000, + 0b0100100001000000, + 0b1010010000110000, + 0b1101001000011100, + 0b0100100000100000, + 0b1010010000011000, + 0b0100100000010000, + 0b1010011011100000, + 0b1101001101111000, + 0b1110100110111110, + 0b0100110011000000, + 0b1010011001110000, + 0b1101001100111100, + 0b0100110001100000, + 0b1010011000111000, + 0b1101001100011110, + 0b0100110000110000, + 0b1010011000011100, + 0b0100111011100000, + 0b1010011101111000, + 0b1101001110111110, + 0b0100111001110000, + 0b1010011100111100, + 0b0100111000111000, + 0b1010011100011110, + 0b0100111101111000, + 0b1010011110111110, + 0b0100111100111100, + 0b0100111100011110, + 0b1010001011000000, + 0b1101000101110000, + 0b1110100010111100, + 0b1010001001100000, + 0b1101000100111000, + 0b1110100010011110, + 0b0100010001000000, + 0b1010001000110000, + 0b1101000100011100, + 0b0100010000100000, + 0b1010001000011000, + 0b0100010000010000, + 0b0100010000001000, + 0b0100011011000000, + 0b1010001101110000, + 0b1101000110111100, + 0b0100011001100000, + 0b1010001100111000, + 0b1101000110011110, + 0b0100011000110000, + 0b1010001100011100, + 0b0100011000011000, + 0b0100011000001100, + 0b0100011101110000, + 0b1010001110111100, + 0b0100011100111000, + 0b1010001110011110, + 0b0100011100011100, + 0b0100011110111100, + 0b1010000101100000, + 0b1101000010111000, + 0b1110100001011110, + 0b0100001001000000, + 0b1010000100110000, + 0b1101000010011100, + 0b0100001000100000, + 0b1010000100011000, + 0b1101000010001110, + 0b0100001000010000, + 0b1010000100001100, + 0b0100001000001000, + 0b1010000100000110, + 0b0100001101100000, + 0b1010000110111000, + 0b1101000011011110, + 0b0100001100110000, + 0b1010000110011100, + 0b0100001100011000, + 0b1010000110001110, + 0b0100001100001100, + 0b0100001100000110, + 0b1010000111011110, + 0b0100001110001110, + 0b0100000101000000, + 0b1010000010110000, + 0b1101000001011100, + 0b0100000100100000, + 0b1010000010011000, + 0b1101000001001110, + 0b0100000100010000, + 0b1010000010001100, + 0b0100000100001000, + 0b1010000010000110, + 0b0100000100000100, + 0b0100000110110000, + 0b0100000110011000, + 0b0100000110001100, + 0b0100000010100000, + 0b1101000000101110, + 0b1010000001001100, + 0b1010000001000110, + 0b0100000010000010, + 0b1100101011100000, + 0b1110010101111000, + 0b1111001010111110, + 0b1001010011000000, + 0b1100101001110000, + 0b1110010100111100, + 0b1001010001100000, + 0b1100101000111000, + 0b1110010100011110, + 0b0010100001000000, + 0b1001010000110000, + 0b0010100000100000, + 0b1001011011100000, + 0b1100101101111000, + 0b1110010110111110, + 0b0010110011000000, + 0b1001011001110000, + 0b1100101100111100, + 0b0010110001100000, + 0b1001011000111000, + 0b0010110000110000, + 0b0010110000011000, + 0b0010111011100000, + 0b1001011101111000, + 0b1100101110111110, + 0b0010111001110000, + 0b1001011100111100, + 0b0010111000111000, + 0b0010111000011100, + 0b0010111101111000, + 0b1001011110111110, + 0b0010111100111100, + 0b0010111110111110, + 0b1101101011000000, + 0b1110110101110000, + 0b1111011010111100, + 0b1101101001100000, + 0b1110110100111000, + 0b1111011010011110, + 0b1011010001000000, + 0b1101101000110000, + 0b1110110100011100, + 0b1011010000100000, + 0b1101101000011000, + 0b1110110100001110, + 0b1011010000010000, + 0b1101101000001100, + 0b1001001011000000, + 0b1100100101110000, + 0b1110010010111100, + 0b1011011011000000, + 0b1001001001100000, + 0b1100100100111000, + 0b1110010010011110, + 0b1011011001100000, + 0b1101101100111000, + 0b1110110110011110, + 0b0110110001000000, + 0b0010010000100000, + 0b1001001000011000, + 0b1100100100001110, + 0b0110110000100000, + 0b1011011000011000, + 0b0110110000010000, + 0b0010011011000000, + 0b1001001101110000, + 0b1100100110111100, + 0b0110111011000000, + 0b0010011001100000, + 0b1001001100111000, + 0b1100100110011110, + 0b0110111001100000, + 0b1011011100111000, + 0b1101101110011110, + 0b0110111000110000, + 0b0010011000011000, + 0b0110111000011000, + 0b0010011101110000, + 0b1001001110111100, + 0b0110111101110000, + 0b0010011100111000, + 0b1001001110011110, + 0b0110111100111000, + 0b1011011110011110, + 0b0110111100011100, + 0b0010011110111100, + 0b0110111110111100, + 0b0010011110011110, + 0b0110111110011110, + 0b1101100101100000, + 0b1110110010111000, + 0b1111011001011110, + 0b1011001001000000, + 0b1101100100110000, + 0b1110110010011100, + 0b1011001000100000, + 0b1101100100011000, + 0b1110110010001110, + 0b1011001000010000, + 0b1101100100001100, + 0b1011001000001000, + 0b1011001000000100, + 0b1001000101100000, + 0b1100100010111000, + 0b1110010001011110, + 0b1011001101100000, + 0b1001000100110000, + 0b1100100010011100, + 0b0110011001000000, + 0b0010001000100000, + 0b1101100110011100, + 0b1100100010001110, + 0b0110011000100000, + 0b0010001000010000, + 0b1001000100001100, + 0b0110011000010000, + 0b1011001100001100, + 0b1001000100000110, + 0b0010001000000100, + 0b0010001101100000, + 0b1001000110111000, + 0b1100100011011110, + 0b0110011101100000, + 0b0010001100110000, + 0b1001000110011100, + 0b0110011100110000, + 0b1011001110011100, + 0b1001000110001110, + 0b0110011100011000, + 0b0010001100001100, + 0b0010001100000110, + 0b0010001110111000, + 0b1001000111011110, + 0b0110011110111000, + 0b0010001110011100, + 0b0110011110011100, + 0b0010001110001110, + 0b0110011110001110, + 0b0110011111011110, + 0b1011000101000000, + 0b1101100010110000, + 0b1110110001011100, + 0b1011000100100000, + 0b1101100010011000, + 0b1110110001001110, + 0b1011000100010000, + 0b1101100010001100, + 0b1011000100001000, + 0b1101100010000110, + 0b1011000100000100, + 0b1011000100000010, + 0b0010000101000000, + 0b1001000010110000, + 0b1100100001011100, + 0b0110001101000000, + 0b0010000100100000, + 0b1001000010011000, + 0b1100100001001110, + 0b0110001100100000, + 0b1011000110011000, + 0b1101100011001110, + 0b0110001100010000, + 0b0010000100001000, + 0b1001000010000110, + 0b0110001100001000, + 0b1011000110000110, + 0b0110001100000100, + 0b0010000110110000, + 0b1001000011011100, + 0b0110001110110000, + 0b0010000110011000, + 0b1001000011001110, + 0b0110001110011000, + 0b1011000111001110, + 0b0110001110001100, + 0b0010000110000110, + 0b0110001110000110, + 0b0110001111011100, + 0b0110001111001110, + 0b1011000010100000, + 0b1101100001011000, + 0b1110110000101110, + 0b1011000010010000, + 0b1101100001001100, + 0b1011000010001000, + 0b1101100001000110, + 0b1011000010000100, + 0b1011000010000010, + 0b0010000010100000, + 0b1001000001011000, + 0b1100100000101110, + 0b0110000110100000, + 0b0010000010010000, + 0b1001000001001100, + 0b0110000110010000, + 0b1011000011001100, + 0b1001000001000110, + 0b0110000110001000, + 0b0010000010000100, + 0b0110000110000100, + 0b0010000010000010, + 0b0010000011011000, + 0b0110000111011000, + 0b0110000111001100, + 0b0110000111000110, + 0b1101100000101100, + 0b1101100000100110, + 0b1011000001000010, + 0b1001000000101100, + 0b0010000001001000, + 0b0110000011001000, + 0b0110000011000100, + 0b0110000011000010, + 0b1000101011000000, + 0b1100010101110000, + 0b1110001010111100, + 0b1000101001100000, + 0b1100010100111000, + 0b0001010001000000, + 0b1000101000110000, + 0b1100010100011100, + 0b0001010000100000, + 0b1000101000011000, + 0b0001010000010000, + 0b0001010000001000, + 0b0001011011000000, + 0b1000101101110000, + 0b1100010110111100, + 0b0001011001100000, + 0b1000101100111000, + 0b1100010110011110, + 0b0001011000110000, + 0b1000101100011100, + 0b0001011000011000, + 0b0001011000001100, + 0b0001011101110000, + 0b1000101110111100, + 0b0001011100111000, + 0b1000101110011110, + 0b0001011100011100, + 0b0001011110111100, + 0b0001011110011110, + 0b1100110101100000, + 0b1110011010111000, + 0b1111001101011110, + 0b1001101001000000, + 0b1100110100110000, + 0b1110011010011100, + 0b1001101000100000, + 0b1100110100011000, + 0b1110011010001110, + 0b1001101000010000, + 0b1100110100001100, + 0b1001101000001000, + 0b1100110100000110, + 0b1000100101100000, + 0b1100010010111000, + 0b1110001001011110, + 0b1001101101100000, + 0b1000100100110000, + 0b1100010010011100, + 0b0011011001000000, + 0b0001001000100000, + 0b1100110110011100, + 0b1100010010001110, + 0b0011011000100000, + 0b1001101100011000, + 0b1000100100001100, + 0b0011011000010000, + 0b0001001000001000, + 0b0011011000001000, + 0b0001001101100000, + 0b1000100110111000, + 0b1100010011011110, + 0b0011011101100000, + 0b0001001100110000, + 0b1100110111011110, + 0b0011011100110000, + 0b1001101110011100, + 0b1000100110001110, + 0b0011011100011000, + 0b0001001100001100, + 0b0011011100001100, + 0b0001001110111000, + 0b1000100111011110, + 0b0011011110111000, + 0b0001001110011100, + 0b0011011110011100, + 0b0001001110001110, + 0b0001001111011110, + 0b0011011111011110, + 0b1101110101000000, + 0b1110111010110000, + 0b1111011101011100, + 0b1101110100100000, + 0b1110111010011000, + 0b1111011101001110, + 0b1101110100010000, + 0b1110111010001100, + 0b1101110100001000, + 0b1110111010000110, + 0b1101110100000100, + 0b1001100101000000, + 0b1100110010110000, + 0b1110011001011100, + 0b1011101101000000, + 0b1001100100100000, + 0b1110111011011100, + 0b1110011001001110, + 0b1011101100100000, + 0b1101110110011000, + 0b1110111011001110, + 0b1011101100010000, + 0b1001100100001000, + 0b1100110010000110, + 0b1011101100001000, + 0b1101110110000110, + 0b1001100100000010, + 0b0001000101000000, + 0b1000100010110000, + 0b1100010001011100, + 0b0011001101000000, + 0b0001000100100000, + 0b1000100010011000, + 0b1100010001001110, + 0b0111011101000000, + 0b0011001100100000, + 0b1001100110011000, + 0b1100110011001110, + 0b0111011100100000, + 0b1011101110011000, + 0b1101110111001110, + 0b1000100010000110, + 0b0111011100010000, + 0b0011001100001000, + 0b1001100110000110, + 0b0111011100001000, + 0b0001000100000010, + 0b0001000110110000, + 0b1000100011011100, + 0b0011001110110000, + 0b0001000110011000, + 0b1000100011001110, + 0b0111011110110000, + 0b0011001110011000, + 0b1001100111001110, + 0b0111011110011000, + 0b1011101111001110, + 0b0001000110000110, + 0b0011001110000110, + 0b0001000111011100, + 0b0011001111011100, + 0b0001000111001110, + 0b0111011111011100, + 0b0011001111001110, + 0b1101110010100000, + 0b1110111001011000, + 0b1111011100101110, + 0b1101110010010000, + 0b1110111001001100, + 0b1101110010001000, + 0b1110111001000110, + 0b1101110010000100, + 0b1101110010000010, + 0b1001100010100000, + 0b1100110001011000, + 0b1110011000101110, + 0b1011100110100000, + 0b1001100010010000, + 0b1110111001101110, + 0b1011100110010000, + 0b1101110011001100, + 0b1100110001000110, + 0b1011100110001000, + 0b1001100010000100, + 0b1011100110000100, + 0b1001100010000010, + 0b1011100110000010, + 0b0001000010100000, + 0b1000100001011000, + 0b1100010000101110, + 0b0011000110100000, + 0b0001000010010000, + 0b1000100001001100, + 0b0111001110100000, + 0b0011000110010000, + 0b1001100011001100, + 0b1000100001000110, + 0b0111001110010000, + 0b1011100111001100, + 0b0001000010000100, + 0b0111001110001000, + 0b0011000110000100, + 0b0001000010000010, + 0b0011000110000010, + 0b0001000011011000, + 0b1000100001101110, + 0b0011000111011000, + 0b0001000011001100, + 0b0111001111011000, + 0b0011000111001100, + 0b0001000011000110, + 0b0111001111001100, + 0b0011000111000110, + 0b0001000011101110, + 0b0111001111101110, + 0b1101110001010000, + 0b1110111000101100, + 0b1101110001001000, + 0b1110111000100110, + 0b1101110001000100, + 0b1101110001000010, + 0b1001100001010000, + 0b1100110000101100, + 0b1011100011010000, + 0b1001100001001000, + 0b1100110000100110, + 0b1011100011001000, + 0b1101110001100110, + 0b1011100011000100, + 0b1001100001000010, + 0b1011100011000010, + 0b0001000001010000, + 0b1000100000101100, + 0b0011000011010000, + 0b0001000001001000, + 0b1000100000100110, + 0b0111000111010000, + 0b0011000011001000, + 0b1001100001100110, + 0b0111000111001000, + 0b1011100011100110, + 0b0001000001000010, + 0b0111000111000100, + 0b0011000011000010, + 0b0111000111000010, + 0b0011000011101100, + 0b0111000111101100, + 0b0111000111100110, + 0b1110111000010110, + 0b1101110000100010, + 0b1100110000010110, + 0b1001100000100100, + 0b1001100000100010, + 0b0001000000101000, + 0b0011000001101000, + 0b0111000011101000, + 0b0001000000100010, + 0b0011000001100010, + 0b1000010101100000, + 0b0000101001000000, + 0b1000010100110000, + 0b0000101000100000, + 0b1000010100011000, + 0b1100001010001110, + 0b0000101000010000, + 0b1000010100001100, + 0b0000101000001000, + 0b1000010100000110, + 0b0000101101100000, + 0b1000010110111000, + 0b1100001011011110, + 0b0000101100110000, + 0b1000010110011100, + 0b0000101100011000, + 0b1000010110001110, + 0b0000101100001100, + 0b0000101100000110, + 0b0000101110111000, + 0b1000010111011110, + 0b0000101110011100, + 0b0000101110001110, + 0b0000101111011110, + 0b1000110101000000, + 0b1100011010110000, + 0b1110001101011100, + 0b1000110100100000, + 0b1100011010011000, + 0b1000110100010000, + 0b1100011010001100, + 0b1000110100001000, + 0b1100011010000110, + 0b1000110100000100, + 0b0000100101000000, + 0b1000010010110000, + 0b1100001001011100, + 0b0001101101000000, + 0b0000100100100000, + 0b1100011011011100, + 0b1100001001001110, + 0b0001101100100000, + 0b1000110110011000, + 0b1100011011001110, + 0b0001101100010000, + 0b0000100100001000, + 0b1000010010000110, + 0b0001101100001000, + 0b1000110110000110, + 0b0000100100000010, + 0b0000100110110000, + 0b1000010011011100, + 0b0001101110110000, + 0b0000100110011000, + 0b1000010011001110, + 0b0001101110011000, + 0b1000110111001110, + 0b0001101110001100, + 0b0000100110000110, + 0b0000100111011100, + 0b0001101111011100, + 0b0000100111001110, + 0b0001101111001110, + 0b1100111010100000, + 0b1110011101011000, + 0b1111001110101110, + 0b1100111010010000, + 0b1110011101001100, + 0b1100111010001000, + 0b1110011101000110, + 0b1100111010000100, + 0b1100111010000010, + 0b1000110010100000, + 0b1100011001011000, + 0b1001110110100000, + 0b1000110010010000, + 0b1100011001001100, + 0b1001110110010000, + 0b1100111011001100, + 0b1100011001000110, + 0b1001110110001000, + 0b1000110010000100, + 0b1001110110000100, + 0b1000110010000010, + 0b1001110110000010, + 0b0000100010100000, + 0b1000010001011000, + 0b0001100110100000, + 0b0000100010010000, + 0b1100011001101110, + 0b0011101110100000, + 0b0001100110010000, + 0b1000110011001100, + 0b1000010001000110, + 0b0011101110010000, + 0b1001110111001100, + 0b0000100010000100, + 0b0011101110001000, + 0b0001100110000100, + 0b0000100010000010, + 0b0001100110000010, + 0b0000100011011000, + 0b1000010001101110, + 0b0001100111011000, + 0b0000100011001100, + 0b0011101111011000, + 0b0001100111001100, + 0b0000100011000110, + 0b0011101111001100, + 0b0001100111000110, + 0b0000100011101110, + 0b0001100111101110, + 0b0011101111101110, + 0b1110111101010000, + 0b1111011110101100, + 0b1110111101001000, + 0b1111011110100110, + 0b1110111101000100, + 0b1110111101000010, + 0b1100111001010000, + 0b1110011100101100, + 0b1101111011010000, + 0b1110111101101100, + 0b1110011100100110, + 0b1101111011001000, + 0b1110111101100110, + 0b1101111011000100, + 0b1100111001000010, + 0b1101111011000010, + 0b1000110001010000, + 0b1100011000101100, + 0b1001110011010000, + 0b1000110001001000, + 0b1100011000100110, + 0b1011110111010000, + 0b1001110011001000, + 0b1100111001100110, + 0b1011110111001000, + 0b1101111011100110, + 0b1000110001000010, + 0b1011110111000100, + 0b1001110011000010, + 0b1011110111000010, + 0b0000100001010000, + 0b1000010000101100, + 0b0001100011010000, + 0b0000100001001000, + 0b1000010000100110, + 0b0011100111010000, + 0b0001100011001000, + 0b1000110001100110, + 0b0111101111010000, + 0b0011100111001000, + 0b1001110011100110, + 0b0000100001000010, + 0b0111101111001000, + 0b1011110111100110, + 0b0001100011000010, + 0b0111101111000100, + 0b0000100001101100, + 0b0001100011101100, + 0b0000100001100110, + 0b0011100111101100, + 0b0001100011100110, + 0b0111101111101100, + 0b0011100111100110, + 0b0111101111100110, + 0b1110111100101000, + 0b1111011110010110, + 0b1110111100100100, + 0b1110111100100010, + 0b1100111000101000, + 0b1110011100010110, + 0b1101111001101000, + 0b1110111100110110, + 0b1101111001100100, + 0b1100111000100010, + 0b1101111001100010, + 0b1000110000101000, + 0b1100011000010110, + 0b1001110001101000, + 0b1000110000100100, + 0b1011110011101000, + 0b1001110001100100, + 0b1000110000100010, + 0b1011110011100100, + 0b1001110001100010, + 0b1011110011100010, + 0b0000100000101000, + 0b1000010000010110, + 0b0001100001101000, + 0b1000110000110110, + 0b0011100011101000, + 0b0001100001100100, + 0b0000100000100010, + 0b0111100111101000, + 0b0011100011100100, + 0b0001100001100010, + 0b0111100111100100, + 0b0011100011100010, + 0b0111100111100010, + 0b0001100001110110, + 0b0111100111110110, + 0b1110111100010010, + 0b1101111000110100, + 0b1101111000110010, + 0b1001110000110100, + 0b1011110001110100, + 0b1011110001110010, + 0b0001100000110100, + 0b0011100001110100, + 0b0111100011110100, + 0b0111100011110010, + 0b0000010101000000, + 0b0000010100100000, + 0b1000001010011000, + 0b0000010100010000, + 0b0000010100001000, + 0b0000010100000100, + 0b0000010110110000, + 0b0000010110011000, + 0b0000010110001100, + 0b0000010110000110, + 0b0000010111011100, + 0b0000010111001110, + 0b1000011010100000, + 0b1000011010010000, + 0b1100001101001100, + 0b1000011010001000, + 0b1100001101000110, + 0b1000011010000100, + 0b1000011010000010, + 0b0000010010100000, + 0b1000001001011000, + 0b0000110110100000, + 0b1000011011011000, + 0b1000001001001100, + 0b0000110110010000, + 0b1000011011001100, + 0b0000110110001000, + 0b1000011011000110, + 0b0000110110000100, + 0b0000010010000010, + 0b0000110110000010, + 0b0000010011011000, + 0b1000001001101110, + 0b0000110111011000, + 0b1000011011101110, + 0b0000110111001100, + 0b0000010011000110, + 0b0000110111000110, + 0b0000010011101110, + 0b0000110111101110, + 0b1100011101010000, + 0b1100011101001000, + 0b1100011101000100, + 0b1100011101000010, + 0b1000011001010000, + 0b1000111011010000, + 0b1100011101101100, + 0b1100001100100110, + 0b1000111011001000, + 0b1100011101100110, + 0b1000111011000100, + 0b1000011001000010, + 0b1000111011000010, + 0b0000010001010000, + 0b0000110011010000, + 0b0000010001001000, + 0b1000001000100110, + 0b0001110111010000, + 0b0000110011001000, + 0b0000010001000100, + 0b0001110111001000, + 0b0000110011000100, + 0b0000010001000010, + 0b0001110111000100, + 0b0000110011000010, + 0b0000010001101100, + 0b0000110011101100, + 0b0000010001100110, + 0b0001110111101100, + 0b0000110011100110, + 0b0001110111100110, + 0b1110011110101000, + 0b1110011110100100, + 0b1110011110100010, + 0b1100011100101000, + 0b1100111101101000, + 0b1110011110110110, + 0b1100111101100100, + 0b1100011100100010, + 0b1100111101100010, + 0b1000011000101000, + 0b1100001100010110, + 0b1000111001101000, + 0b1100011100110110, + 0b1001111011101000, + 0b1000111001100100, + 0b1000011000100010, + 0b1001111011100100, + 0b1000111001100010, + 0b1001111011100010, + 0b0000010000101000, + 0b1000001000010110, + 0b0000110001101000, + 0b1000011000110110, + 0b0001110011101000, + 0b0000110001100100, + 0b0000010000100010, + 0b0011110111101000, + 0b0001110011100100, + 0b0000110001100010, + 0b0011110111100100, + 0b0001110011100010, + 0b0000010000110110, + 0b0000110001110110, + 0b0001110011110110, + 0b0011110111110110, + 0b1111011111010100, + 0b1111011111010010, + 0b1110011110010100, + 0b1110111110110100, + 0b1110011110010010, + 0b1110111110110010, + 0b1100011100010100, + 0b1100111100110100, + 0b1100011100010010, + 0b1101111101110100, + 0b1100111100110010, + 0b1101111101110010, + 0b1000011000010100, + 0b1000111000110100, + 0b1000011000010010, + 0b1001111001110100, + 0b1000111000110010, + 0b1011111011110100, + + // Table 2 + 0b1111010101100000, + 0b1111101010111000, + 0b1110101001000000, + 0b1111010100110000, + 0b1111101010011100, + 0b1110101000100000, + 0b1111010100011000, + 0b1111101010001110, + 0b1110101000010000, + 0b1111010100001100, + 0b1110101000001000, + 0b1111010100000110, + 0b1110101000000100, + 0b1110101101100000, + 0b1111010110111000, + 0b1111101011011110, + 0b1101011001000000, + 0b1110101100110000, + 0b1111010110011100, + 0b1101011000100000, + 0b1110101100011000, + 0b1111010110001110, + 0b1101011000010000, + 0b1110101100001100, + 0b1101011000001000, + 0b1110101100000110, + 0b1101011000000100, + 0b1101011101100000, + 0b1110101110111000, + 0b1111010111011110, + 0b1010111001000000, + 0b1101011100110000, + 0b1110101110011100, + 0b1010111000100000, + 0b1101011100011000, + 0b1110101110001110, + 0b1010111000010000, + 0b1101011100001100, + 0b1010111000001000, + 0b1101011100000110, + 0b1010111000000100, + 0b1010111101100000, + 0b1101011110111000, + 0b1110101111011110, + 0b0101111001000000, + 0b1010111100110000, + 0b1101011110011100, + 0b0101111000100000, + 0b1010111100011000, + 0b1101011110001110, + 0b0101111000010000, + 0b1010111100001100, + 0b0101111000001000, + 0b1010111100000110, + 0b0101111101100000, + 0b1010111110111000, + 0b1101011111011110, + 0b0101111100110000, + 0b1010111110011100, + 0b0101111100011000, + 0b1010111110001110, + 0b0101111100001100, + 0b0101111110111000, + 0b1010111111011110, + 0b0101111110011100, + 0b0101111110001110, + 0b1110100101000000, + 0b1111010010110000, + 0b1111101001011100, + 0b1110100100100000, + 0b1111010010011000, + 0b1111101001001110, + 0b1110100100010000, + 0b1111010010001100, + 0b1110100100001000, + 0b1111010010000110, + 0b1110100100000100, + 0b1110100100000010, + 0b1101001101000000, + 0b1110100110110000, + 0b1111010011011100, + 0b1101001100100000, + 0b1110100110011000, + 0b1111010011001110, + 0b1101001100010000, + 0b1110100110001100, + 0b1101001100001000, + 0b1110100110000110, + 0b1101001100000100, + 0b1101001100000010, + 0b1010011101000000, + 0b1101001110110000, + 0b1110100111011100, + 0b1010011100100000, + 0b1101001110011000, + 0b1110100111001110, + 0b1010011100010000, + 0b1101001110001100, + 0b1010011100001000, + 0b1101001110000110, + 0b1010011100000100, + 0b1010011100000010, + 0b0100111101000000, + 0b1010011110110000, + 0b1101001111011100, + 0b0100111100100000, + 0b1010011110011000, + 0b1101001111001110, + 0b0100111100010000, + 0b1010011110001100, + 0b0100111100001000, + 0b1010011110000110, + 0b0100111100000100, + 0b0100111110110000, + 0b1010011111011100, + 0b0100111110011000, + 0b1010011111001110, + 0b0100111110001100, + 0b0100111110000110, + 0b0100111111011100, + 0b0100111111001110, + 0b1110100010100000, + 0b1111010001011000, + 0b1111101000101110, + 0b1110100010010000, + 0b1111010001001100, + 0b1110100010001000, + 0b1111010001000110, + 0b1110100010000100, + 0b1110100010000010, + 0b1101000110100000, + 0b1110100011011000, + 0b1111010001101110, + 0b1101000110010000, + 0b1110100011001100, + 0b1101000110001000, + 0b1110100011000110, + 0b1101000110000100, + 0b1101000110000010, + 0b1010001110100000, + 0b1101000111011000, + 0b1110100011101110, + 0b1010001110010000, + 0b1101000111001100, + 0b1010001110001000, + 0b1101000111000110, + 0b1010001110000100, + 0b1010001110000010, + 0b0100011110100000, + 0b1010001111011000, + 0b1101000111101110, + 0b0100011110010000, + 0b1010001111001100, + 0b0100011110001000, + 0b1010001111000110, + 0b0100011110000100, + 0b0100011110000010, + 0b0100011111011000, + 0b1010001111101110, + 0b0100011111001100, + 0b0100011111000110, + 0b0100011111101110, + 0b1110100001010000, + 0b1111010000101100, + 0b1110100001001000, + 0b1111010000100110, + 0b1110100001000100, + 0b1110100001000010, + 0b1101000011010000, + 0b1110100001101100, + 0b1101000011001000, + 0b1110100001100110, + 0b1101000011000100, + 0b1101000011000010, + 0b1010000111010000, + 0b1101000011101100, + 0b1010000111001000, + 0b1101000011100110, + 0b1010000111000100, + 0b1010000111000010, + 0b0100001111010000, + 0b1010000111101100, + 0b0100001111001000, + 0b1010000111100110, + 0b0100001111000100, + 0b0100001111000010, + 0b0100001111101100, + 0b0100001111100110, + 0b1110100000101000, + 0b1111010000010110, + 0b1110100000100100, + 0b1110100000100010, + 0b1101000001101000, + 0b1110100000110110, + 0b1101000001100100, + 0b1101000001100010, + 0b1010000011101000, + 0b1101000001110110, + 0b1010000011100100, + 0b1010000011100010, + 0b0100000111101000, + 0b1010000011110110, + 0b0100000111100100, + 0b0100000111100010, + 0b1110100000010100, + 0b1110100000010010, + 0b1101000000110100, + 0b1101000000110010, + 0b1010000001110100, + 0b1010000001110010, + 0b1110010101000000, + 0b1111001010110000, + 0b1111100101011100, + 0b1110010100100000, + 0b1111001010011000, + 0b1111100101001110, + 0b1110010100010000, + 0b1111001010001100, + 0b1110010100001000, + 0b1111001010000110, + 0b1110010100000100, + 0b1110010100000010, + 0b1100101101000000, + 0b1110010110110000, + 0b1111001011011100, + 0b1100101100100000, + 0b1110010110011000, + 0b1111001011001110, + 0b1100101100010000, + 0b1110010110001100, + 0b1100101100001000, + 0b1110010110000110, + 0b1100101100000100, + 0b1100101100000010, + 0b1001011101000000, + 0b1100101110110000, + 0b1110010111011100, + 0b1001011100100000, + 0b1100101110011000, + 0b1110010111001110, + 0b1001011100010000, + 0b1100101110001100, + 0b1001011100001000, + 0b1100101110000110, + 0b1001011100000100, + 0b1001011100000010, + 0b0010111101000000, + 0b1001011110110000, + 0b1100101111011100, + 0b0010111100100000, + 0b1001011110011000, + 0b1100101111001110, + 0b0010111100010000, + 0b1001011110001100, + 0b0010111100001000, + 0b1001011110000110, + 0b0010111100000100, + 0b0010111110110000, + 0b1001011111011100, + 0b0010111110011000, + 0b1001011111001110, + 0b0010111110001100, + 0b0010111110000110, + 0b0010111111011100, + 0b0010111111001110, + 0b1111011010100000, + 0b1111101101011000, + 0b0110101111110000, + 0b1111011010010000, + 0b1111101101001100, + 0b0110100111111000, + 0b1111011010001000, + 0b1111101101000110, + 0b0110100011111100, + 0b1111011010000100, + 0b1111011010000010, + 0b1110010010100000, + 0b1111001001011000, + 0b1111100100101110, + 0b1110110110100000, + 0b1110010010010000, + 0b1111101101101110, + 0b1110110110010000, + 0b1111011011001100, + 0b1111001001000110, + 0b1110110110001000, + 0b1110010010000100, + 0b1110110110000100, + 0b1110010010000010, + 0b1110110110000010, + 0b1100100110100000, + 0b1110010011011000, + 0b1111001001101110, + 0b1101101110100000, + 0b1100100110010000, + 0b1110010011001100, + 0b1101101110010000, + 0b1110110111001100, + 0b1110010011000110, + 0b1101101110001000, + 0b1100100110000100, + 0b1101101110000100, + 0b1100100110000010, + 0b1101101110000010, + 0b1001001110100000, + 0b1100100111011000, + 0b1110010011101110, + 0b1011011110100000, + 0b1001001110010000, + 0b1100100111001100, + 0b1011011110010000, + 0b1101101111001100, + 0b1100100111000110, + 0b1011011110001000, + 0b1001001110000100, + 0b1011011110000100, + 0b1001001110000010, + 0b1011011110000010, + 0b0010011110100000, + 0b1001001111011000, + 0b1100100111101110, + 0b0110111110100000, + 0b0010011110010000, + 0b1001001111001100, + 0b0110111110010000, + 0b1011011111001100, + 0b1001001111000110, + 0b0110111110001000, + 0b0010011110000100, + 0b0110111110000100, + 0b0010011110000010, + 0b0010011111011000, + 0b1001001111101110, + 0b0110111111011000, + 0b0010011111001100, + 0b0110111111001100, + 0b0010011111000110, + 0b0110111111000110, + 0b0010011111101110, + 0b1111011001010000, + 0b1111101100101100, + 0b0110010111111000, + 0b1111011001001000, + 0b1111101100100110, + 0b0110010011111100, + 0b1111011001000100, + 0b0110010001111110, + 0b1111011001000010, + 0b1110010001010000, + 0b1111001000101100, + 0b1110110011010000, + 0b1110010001001000, + 0b1111001000100110, + 0b1110110011001000, + 0b1111011001100110, + 0b1110110011000100, + 0b1110010001000010, + 0b1110110011000010, + 0b1100100011010000, + 0b1110010001101100, + 0b1101100111010000, + 0b1100100011001000, + 0b1110010001100110, + 0b1101100111001000, + 0b1110110011100110, + 0b1101100111000100, + 0b1100100011000010, + 0b1101100111000010, + 0b1001000111010000, + 0b1100100011101100, + 0b1011001111010000, + 0b1001000111001000, + 0b1100100011100110, + 0b1011001111001000, + 0b1101100111100110, + 0b1011001111000100, + 0b1001000111000010, + 0b1011001111000010, + 0b0010001111010000, + 0b1001000111101100, + 0b0110011111010000, + 0b0010001111001000, + 0b1001000111100110, + 0b0110011111001000, + 0b1011001111100110, + 0b0110011111000100, + 0b0010001111000010, + 0b0110011111000010, + 0b0010001111101100, + 0b0110011111101100, + 0b0010001111100110, + 0b0110011111100110, + 0b1111011000101000, + 0b1111101100010110, + 0b0110001011111100, + 0b1111011000100100, + 0b0110001001111110, + 0b1111011000100010, + 0b1110010000101000, + 0b1111001000010110, + 0b1110110001101000, + 0b1111011000110110, + 0b1110110001100100, + 0b1110010000100010, + 0b1110110001100010, + 0b1100100001101000, + 0b1110010000110110, + 0b1101100011101000, + 0b1100100001100100, + 0b1101100011100100, + 0b1100100001100010, + 0b1101100011100010, + 0b1001000011101000, + 0b1100100001110110, + 0b1011000111101000, + 0b1101100011110110, + 0b1011000111100100, + 0b1001000011100010, + 0b1011000111100010, + 0b0010000111101000, + 0b1001000011110110, + 0b0110001111101000, + 0b0010000111100100, + 0b0110001111100100, + 0b0010000111100010, + 0b0110001111100010, + 0b0010000111110110, + 0b0110001111110110, + 0b1111011000010100, + 0b0110000101111110, + 0b1111011000010010, + 0b1110010000010100, + 0b1110110000110100, + 0b1110010000010010, + 0b1110110000110010, + 0b1100100000110100, + 0b1101100001110100, + 0b1100100000110010, + 0b1101100001110010, + 0b1001000001110100, + 0b1011000011110100, + 0b1001000001110010, + 0b1011000011110010, + 0b0010000011110100, + 0b0110000111110100, + 0b0010000011110010, + 0b0110000111110010, + 0b1111011000001010, + 0b1110010000001010, + 0b1110110000011010, + 0b1100100000011010, + 0b1101100000111010, + 0b1001000000111010, + 0b1011000001111010, + 0b1110001010100000, + 0b1111000101011000, + 0b1111100010101110, + 0b1110001010010000, + 0b1111000101001100, + 0b1110001010001000, + 0b1111000101000110, + 0b1110001010000100, + 0b1110001010000010, + 0b1100010110100000, + 0b1110001011011000, + 0b1111000101101110, + 0b1100010110010000, + 0b1110001011001100, + 0b1100010110001000, + 0b1110001011000110, + 0b1100010110000100, + 0b1100010110000010, + 0b1000101110100000, + 0b1100010111011000, + 0b1110001011101110, + 0b1000101110010000, + 0b1100010111001100, + 0b1000101110001000, + 0b1100010111000110, + 0b1000101110000100, + 0b1000101110000010, + 0b0001011110100000, + 0b1000101111011000, + 0b1100010111101110, + 0b0001011110010000, + 0b1000101111001100, + 0b0001011110001000, + 0b1000101111000110, + 0b0001011110000100, + 0b0001011110000010, + 0b0001011111011000, + 0b1000101111101110, + 0b0001011111001100, + 0b0001011111000110, + 0b0001011111101110, + 0b1111001101010000, + 0b1111100110101100, + 0b0011010111111000, + 0b1111001101001000, + 0b1111100110100110, + 0b0011010011111100, + 0b1111001101000100, + 0b0011010001111110, + 0b1111001101000010, + 0b1110001001010000, + 0b1111000100101100, + 0b1110011011010000, + 0b1110001001001000, + 0b1111000100100110, + 0b1110011011001000, + 0b1111001101100110, + 0b1110011011000100, + 0b1110001001000010, + 0b1110011011000010, + 0b1100010011010000, + 0b1110001001101100, + 0b1100110111010000, + 0b1100010011001000, + 0b1110001001100110, + 0b1100110111001000, + 0b1110011011100110, + 0b1100110111000100, + 0b1100010011000010, + 0b1100110111000010, + 0b1000100111010000, + 0b1100010011101100, + 0b1001101111010000, + 0b1000100111001000, + 0b1100010011100110, + 0b1001101111001000, + 0b1100110111100110, + 0b1001101111000100, + 0b1000100111000010, + 0b1001101111000010, + 0b0001001111010000, + 0b1000100111101100, + 0b0011011111010000, + 0b0001001111001000, + 0b1000100111100110, + 0b0011011111001000, + 0b1001101111100110, + 0b0011011111000100, + 0b0001001111000010, + 0b0011011111000010, + 0b0001001111101100, + 0b0011011111101100, + 0b0001001111100110, + 0b0011011111100110, + 0b1111101110101000, + 0b0111010111110000, + 0b1011101011111100, + 0b1111101110100100, + 0b0111010011111000, + 0b1011101001111110, + 0b1111101110100010, + 0b0111010001111100, + 0b0111010000111110, + 0b1111001100101000, + 0b1111100110010110, + 0b0011001011111100, + 0b1111011101101000, + 0b1111101110110110, + 0b0111011011111100, + 0b0011001001111110, + 0b1111011101100100, + 0b1111001100100010, + 0b0111011001111110, + 0b1111011101100010, + 0b1110001000101000, + 0b1111000100010110, + 0b1110011001101000, + 0b1110001000100100, + 0b1110111011101000, + 0b1111011101110110, + 0b1110001000100010, + 0b1110111011100100, + 0b1110011001100010, + 0b1110111011100010, + 0b1100010001101000, + 0b1110001000110110, + 0b1100110011101000, + 0b1100010001100100, + 0b1101110111101000, + 0b1100110011100100, + 0b1100010001100010, + 0b1101110111100100, + 0b1100110011100010, + 0b1101110111100010, + 0b1000100011101000, + 0b1100010001110110, + 0b1001100111101000, + 0b1000100011100100, + 0b1011101111101000, + 0b1001100111100100, + 0b1000100011100010, + 0b1011101111100100, + 0b1001100111100010, + 0b1011101111100010, + 0b0001000111101000, + 0b1000100011110110, + 0b0011001111101000, + 0b0001000111100100, + 0b0111011111101000, + 0b0011001111100100, + 0b0001000111100010, + 0b0111011111100100, + 0b0011001111100010, + 0b0111011111100010, + 0b0001000111110110, + 0b0011001111110110, + 0b1111101110010100, + 0b0111001011111000, + 0b1011100101111110, + 0b1111101110010010, + 0b0111001001111100, + 0b0111001000111110, + 0b1111001100010100, + 0b0011000101111110, + 0b1111011100110100, + 0b1111001100010010, + 0b0111001101111110, + 0b1111011100110010, + 0b1110001000010100, + 0b1110011000110100, + 0b1110001000010010, + 0b1110111001110100, + 0b1110011000110010, + 0b1110111001110010, + 0b1100010000110100, + 0b1100110001110100, + 0b1100010000110010, + 0b1101110011110100, + 0b1100110001110010, + 0b1101110011110010, + 0b1000100001110100, + 0b1001100011110100, + 0b1000100001110010, + 0b1011100111110100, + 0b1001100011110010, + 0b1011100111110010, + 0b0001000011110100, + 0b0011000111110100, + 0b0001000011110010, + 0b0111001111110100, + 0b0011000111110010, + 0b0111001111110010, + 0b1111101110001010, + 0b0111000101111100, + 0b0111000100111110, + 0b1111001100001010, + 0b1111011100011010, + 0b1110001000001010, + 0b1110011000011010, + 0b1110111000111010, + 0b1100010000011010, + 0b1100110000111010, + 0b1101110001111010, + 0b1000100000111010, + 0b1001100001111010, + 0b1011100011111010, + 0b0001000001111010, + 0b0011000011111010, + 0b0111000111111010, + 0b0111000010111110, + 0b1110000101010000, + 0b1111000010101100, + 0b1110000101001000, + 0b1111000010100110, + 0b1110000101000100, + 0b1110000101000010, + 0b1100001011010000, + 0b1110000101101100, + 0b1100001011001000, + 0b1110000101100110, + 0b1100001011000100, + 0b1100001011000010, + 0b1000010111010000, + 0b1100001011101100, + 0b1000010111001000, + 0b1100001011100110, + 0b1000010111000100, + 0b1000010111000010, + 0b0000101111010000, + 0b1000010111101100, + 0b0000101111001000, + 0b1000010111100110, + 0b0000101111000100, + 0b0000101111000010, + 0b0000101111101100, + 0b0000101111100110, + 0b1111000110101000, + 0b1111100011010110, + 0b0001101011111100, + 0b1111000110100100, + 0b0001101001111110, + 0b1111000110100010, + 0b1110000100101000, + 0b1111000010010110, + 0b1110001101101000, + 0b1110000100100100, + 0b1110001101100100, + 0b1110000100100010, + 0b1110001101100010, + 0b1100001001101000, + 0b1110000100110110, + 0b1100011011101000, + 0b1100001001100100, + 0b1100011011100100, + 0b1100001001100010, + 0b1100011011100010, + 0b1000010011101000, + 0b1100001001110110, + 0b1000110111101000, + 0b1000010011100100, + 0b1000110111100100, + 0b1000010011100010, + 0b1000110111100010, + 0b0000100111101000, + 0b1000010011110110, + 0b0001101111101000, + 0b0000100111100100, + 0b0001101111100100, + 0b0000100111100010, + 0b0001101111100010, + 0b0000100111110110, + 0b0001101111110110, + 0b1111100111010100, + 0b0011101011111000, + 0b1001110101111110, + 0b1111100111010010, + 0b0011101001111100, + 0b0011101000111110, + 0b1111000110010100, + 0b0001100101111110, + 0b1111001110110100, + 0b1111000110010010, + 0b0011101101111110, + 0b1111001110110010, + 0b1110000100010100, + 0b1110001100110100, + 0b1110000100010010, + 0b1110011101110100, + 0b1110001100110010, + 0b1110011101110010, + 0b1100001000110100, + 0b1100011001110100, + 0b1100001000110010, + 0b1100111011110100, + 0b1100011001110010, + 0b1100111011110010, + 0b1000010001110100, + 0b1000110011110100, + 0b1000010001110010, + 0b1001110111110100, + 0b1000110011110010, + 0b1001110111110010, + 0b0000100011110100, + 0b0001100111110100, + 0b0000100011110010, + 0b0011101111110100, + 0b0001100111110010, + 0b0011101111110010, + 0b0111101011110000, + 0b1011110101111100, + 0b0111101001111000, + 0b1011110100111110, + 0b0111101000111100, + 0b0111101000011110, + 0b1111100111001010, + 0b0011100101111100, + 0b1111101111011010, + 0b0111101101111100, + 0b0011100100111110, + 0b0111101100111110, + 0b1111000110001010, + 0b1111001110011010, + 0b1111011110111010, + 0b1110000100001010, + 0b1110001100011010, + 0b1110011100111010, + 0b1110111101111010, + 0b1100001000011010, + 0b1100011000111010, + 0b1100111001111010, + 0b1101111011111010, + 0b1000010000111010, + 0b1000110001111010, + 0b1001110011111010, + 0b1011110111111010, + 0b0000100001111010, + 0b0001100011111010, + 0b0011100111111010, + 0b0111100101111000, + 0b1011110010111110, + 0b0111100100111100, + 0b0111100100011110, + 0b0011100010111110, + 0b0111100110111110, + 0b0111100010111100, + 0b0111100010011110, + 0b0111100001011110, + 0b1110000010101000, + 0b1110000010100100, + 0b1110000010100010, + 0b1100000101101000, + 0b1110000010110110, + 0b1100000101100100, + 0b1100000101100010, + 0b1000001011101000, + 0b1100000101110110, + 0b1000001011100100, + 0b1000001011100010, + 0b0000010111101000, + 0b1000001011110110, + 0b0000010111100100, + 0b0000010111100010, + 0b0000010111110110, + 0b1111000011010100, + 0b0000110101111110, + 0b1111000011010010, + 0b1110000010010100, + 0b1110000110110100, + 0b1110000010010010, + 0b1110000110110010, + 0b1100000100110100, + 0b1100001101110100, + 0b1100000100110010, + 0b1100001101110010, + 0b1000001001110100, + 0b1000011011110100, + 0b1000001001110010, + 0b1000011011110010, + 0b0000010011110100, + 0b0000110111110100, + 0b0000010011110010, + 0b0000110111110010, + 0b1111100011101010, + 0b0001110101111100, + 0b0001110100111110, + 0b1111000011001010, + 0b1111000111011010, + 0b1110000010001010, + 0b1110000110011010, + 0b1110001110111010, + 0b1100000100011010, + 0b1100001100111010, + 0b1100011101111010, + 0b1000001000111010, + 0b1000011001111010, + 0b1000111011111010, + 0b0000010001111010, + 0b0000110011111010, + 0b0001110111111010, + 0b0011110101111000, + 0b1001111010111110, + 0b0011110100111100, + 0b0011110100011110, + 0b0001110010111110, + 0b0011110110111110, + 0b0111110101110000, + 0b1011111010111100, + 0b0111110100111000, + 0b1011111010011110, + 0b0111110100011100, + 0b0111110100001110, + 0b0011110010111100, + 0b0111110110111100, + 0b0011110010011110, + 0b0111110110011110, + 0b0111110010111000, + 0b1011111001011110, + 0b0111110010011100, + 0b0111110010001110, + 0b0011110001011110, + 0b0111110011011110, + 0b0111110001011100, + 0b0111110001001110, + 0b0111110000101110, + 0b1100000010110100, + 0b1100000010110010, + 0b1000000101110100, + 0b1000000101110010, + 0b0000001011110100, + 0b0000001011110010, + 0b1110000011011010, + 0b1100000010011010, + 0b1100000110111010, + 0b1000000100111010, + 0b1000001101111010, + 0b0000001001111010, + 0b0000011011111010, + 0b0000111010111110, + 0b0001111010111100, + 0b0001111010011110, + 0b0011111010111000, + 0b1001111101011110, + 0b0011111010011100, + 0b0011111010001110, + 0b0001111001011110, + 0b0011111011011110, + 0b0111111010110000, + 0b1011111101011100, + 0b0111111010011000, + 0b1011111101001110, + 0b0111111010001100, + 0b0111111010000110, + 0b0011111001011100, + 0b0111111011011100, + 0b0011111001001110, + 0b0111111011001110, + 0b0111111001011000, + 0b1011111100101110, + 0b0111111001001100, + 0b0111111001000110, + 0b0011111000101110, + 0b0111111001101110, + 0b0111111000101100, + 0b0111111000100110, + 0b0000111101011110, + 0b0001111101011100, + 0b0001111101001110, + 0b0011111101011000, + 0b1001111110101110, + 0b0011111101001100, + 0b0011111101000110, + 0b0001111100101110, + 0b0011111101101110, + 0b0011111100101100, + 0b0011111100100110, + + // Table 3 + 0b1010101111100000, + 0b1101010111111000, + 0b0101001111000000, + 0b1010100111110000, + 0b1101010011111100, + 0b0101000111100000, + 0b1010100011111000, + 0b1101010001111110, + 0b0101000011110000, + 0b1010100001111100, + 0b0101000001111000, + 0b1111101011010000, + 0b0101101111100000, + 0b1010110111111000, + 0b1111101011001000, + 0b0101100111110000, + 0b1010110011111100, + 0b1111101011000100, + 0b0101100011111000, + 0b1010110001111110, + 0b1111101011000010, + 0b0101100001111100, + 0b1111010111010000, + 0b1111101011101100, + 0b0101110111111000, + 0b1111010111001000, + 0b1111101011100110, + 0b0101110011111100, + 0b1111010111000100, + 0b0101110001111110, + 0b1111010111000010, + 0b1110101111010000, + 0b1111010111101100, + 0b1110101111001000, + 0b1111010111100110, + 0b1110101111000100, + 0b1110101111000010, + 0b1101011111010000, + 0b1110101111101100, + 0b1101011111001000, + 0b1110101111100110, + 0b1101011111000100, + 0b1101011111000010, + 0b1010111111010000, + 0b1101011111101100, + 0b1010111111001000, + 0b1101011111100110, + 0b1010111111000100, + 0b0100101111000000, + 0b1010010111110000, + 0b1101001011111100, + 0b0100100111100000, + 0b1010010011111000, + 0b1101001001111110, + 0b0100100011110000, + 0b1010010001111100, + 0b0100100001111000, + 0b1010010000111110, + 0b0100100000111100, + 0b1111101001101000, + 0b0100110111110000, + 0b1010011011111100, + 0b1111101001100100, + 0b0100110011111000, + 0b1010011001111110, + 0b1111101001100010, + 0b0100110001111100, + 0b0100110000111110, + 0b1111010011101000, + 0b1111101001110110, + 0b0100111011111100, + 0b1111010011100100, + 0b0100111001111110, + 0b1111010011100010, + 0b1110100111101000, + 0b1111010011110110, + 0b1110100111100100, + 0b1110100111100010, + 0b1101001111101000, + 0b1110100111110110, + 0b1101001111100100, + 0b1101001111100010, + 0b1010011111101000, + 0b1101001111110110, + 0b1010011111100100, + 0b1010011111100010, + 0b0100010111100000, + 0b1010001011111000, + 0b1101000101111110, + 0b0100010011110000, + 0b1010001001111100, + 0b0100010001111000, + 0b1010001000111110, + 0b0100010000111100, + 0b0100010000011110, + 0b1111101000110100, + 0b0100011011111000, + 0b1010001101111110, + 0b1111101000110010, + 0b0100011001111100, + 0b0100011000111110, + 0b1111010001110100, + 0b0100011101111110, + 0b1111010001110010, + 0b1110100011110100, + 0b1110100011110010, + 0b1101000111110100, + 0b1101000111110010, + 0b1010001111110100, + 0b1010001111110010, + 0b0100001011110000, + 0b1010000101111100, + 0b0100001001111000, + 0b1010000100111110, + 0b0100001000111100, + 0b0100001000011110, + 0b1111101000011010, + 0b0100001101111100, + 0b0100001100111110, + 0b1111010000111010, + 0b1110100001111010, + 0b1101000011111010, + 0b0100000101111000, + 0b1010000010111110, + 0b0100000100111100, + 0b0100000100011110, + 0b0100000110111110, + 0b0100000010111100, + 0b0100000010011110, + 0b0010101111000000, + 0b1001010111110000, + 0b1100101011111100, + 0b0010100111100000, + 0b1001010011111000, + 0b1100101001111110, + 0b0010100011110000, + 0b1001010001111100, + 0b0010100001111000, + 0b1001010000111110, + 0b0010100000111100, + 0b1111100101101000, + 0b0010110111110000, + 0b1001011011111100, + 0b1111100101100100, + 0b0010110011111000, + 0b1001011001111110, + 0b1111100101100010, + 0b0010110001111100, + 0b0010110000111110, + 0b1111001011101000, + 0b1111100101110110, + 0b0010111011111100, + 0b1111001011100100, + 0b0010111001111110, + 0b1111001011100010, + 0b1110010111101000, + 0b1111001011110110, + 0b1110010111100100, + 0b1110010111100010, + 0b1100101111101000, + 0b1110010111110110, + 0b1100101111100100, + 0b1100101111100010, + 0b1001011111101000, + 0b1100101111110110, + 0b1001011111100100, + 0b1001011111100010, + 0b1011010111100000, + 0b1101101011111000, + 0b1110110101111110, + 0b0110100111000000, + 0b1011010011110000, + 0b1101101001111100, + 0b0110100011100000, + 0b1011010001111000, + 0b1101101000111110, + 0b0110100001110000, + 0b1011010000111100, + 0b0110100000111000, + 0b1011010000011110, + 0b0110100000011100, + 0b0010010111100000, + 0b1001001011111000, + 0b1100100101111110, + 0b0110110111100000, + 0b0010010011110000, + 0b1001001001111100, + 0b0110110011110000, + 0b1011011001111100, + 0b1001001000111110, + 0b0110110001111000, + 0b0010010000111100, + 0b0110110000111100, + 0b0010010000011110, + 0b0110110000011110, + 0b1111100100110100, + 0b0010011011111000, + 0b1001001101111110, + 0b1111101101110100, + 0b1111100100110010, + 0b0110111011111000, + 0b0010011001111100, + 0b1111101101110010, + 0b0110111001111100, + 0b0010011000111110, + 0b0110111000111110, + 0b1111001001110100, + 0b0010011101111110, + 0b1111011011110100, + 0b1111001001110010, + 0b0110111101111110, + 0b1111011011110010, + 0b1110010011110100, + 0b1110110111110100, + 0b1110010011110010, + 0b1110110111110010, + 0b1100100111110100, + 0b1101101111110100, + 0b1100100111110010, + 0b1101101111110010, + 0b1001001111110100, + 0b1001001111110010, + 0b0110010111000000, + 0b1011001011110000, + 0b1101100101111100, + 0b0110010011100000, + 0b1011001001111000, + 0b1101100100111110, + 0b0110010001110000, + 0b1011001000111100, + 0b0110010000111000, + 0b1011001000011110, + 0b0110010000011100, + 0b0110010000001110, + 0b0010001011110000, + 0b1001000101111100, + 0b0110011011110000, + 0b0010001001111000, + 0b1001000100111110, + 0b0110011001111000, + 0b1011001100111110, + 0b0110011000111100, + 0b0010001000011110, + 0b0110011000011110, + 0b1111100100011010, + 0b0010001101111100, + 0b1111101100111010, + 0b0110011101111100, + 0b0010001100111110, + 0b0110011100111110, + 0b1111001000111010, + 0b1111011001111010, + 0b1110010001111010, + 0b1110110011111010, + 0b1100100011111010, + 0b1101100111111010, + 0b1001000111111010, + 0b0110001011100000, + 0b1011000101111000, + 0b1101100010111110, + 0b0110001001110000, + 0b1011000100111100, + 0b0110001000111000, + 0b1011000100011110, + 0b0110001000011100, + 0b0110001000001110, + 0b0010000101111000, + 0b1001000010111110, + 0b0110001101111000, + 0b0010000100111100, + 0b0110001100111100, + 0b0010000100011110, + 0b0110001100011110, + 0b0010000110111110, + 0b0110001110111110, + 0b0110000101110000, + 0b1011000010111100, + 0b0110000100111000, + 0b1011000010011110, + 0b0110000100011100, + 0b0110000100001110, + 0b0010000010111100, + 0b0110000110111100, + 0b0010000010011110, + 0b0110000110011110, + 0b0110000010111000, + 0b1011000001011110, + 0b0110000010011100, + 0b0110000010001110, + 0b0010000001011110, + 0b0110000011011110, + 0b0110000001011100, + 0b0110000001001110, + 0b0001010111100000, + 0b1000101011111000, + 0b1100010101111110, + 0b0001010011110000, + 0b1000101001111100, + 0b0001010001111000, + 0b1000101000111110, + 0b0001010000111100, + 0b0001010000011110, + 0b1111100010110100, + 0b0001011011111000, + 0b1000101101111110, + 0b1111100010110010, + 0b0001011001111100, + 0b0001011000111110, + 0b1111000101110100, + 0b0001011101111110, + 0b1111000101110010, + 0b1110001011110100, + 0b1110001011110010, + 0b1100010111110100, + 0b1100010111110010, + 0b1000101111110100, + 0b1000101111110010, + 0b0011010111000000, + 0b1001101011110000, + 0b1100110101111100, + 0b0011010011100000, + 0b1001101001111000, + 0b1100110100111110, + 0b0011010001110000, + 0b1001101000111100, + 0b0011010000111000, + 0b1001101000011110, + 0b0011010000011100, + 0b0011010000001110, + 0b0001001011110000, + 0b1000100101111100, + 0b0011011011110000, + 0b0001001001111000, + 0b1000100100111110, + 0b0011011001111000, + 0b1001101100111110, + 0b0011011000111100, + 0b0001001000011110, + 0b0011011000011110, + 0b1111100010011010, + 0b0001001101111100, + 0b1111100110111010, + 0b0011011101111100, + 0b0001001100111110, + 0b0011011100111110, + 0b1111000100111010, + 0b1111001101111010, + 0b1110001001111010, + 0b1110011011111010, + 0b1100010011111010, + 0b1100110111111010, + 0b1000100111111010, + 0b1011101011100000, + 0b1101110101111000, + 0b1110111010111110, + 0b0111010011000000, + 0b1011101001110000, + 0b1101110100111100, + 0b0111010001100000, + 0b1011101000111000, + 0b1101110100011110, + 0b0111010000110000, + 0b1011101000011100, + 0b0111010000011000, + 0b1011101000001110, + 0b0111010000001100, + 0b0011001011100000, + 0b1001100101111000, + 0b1100110010111110, + 0b0111011011100000, + 0b0011001001110000, + 0b1001100100111100, + 0b0111011001110000, + 0b1011101100111100, + 0b1001100100011110, + 0b0111011000111000, + 0b0011001000011100, + 0b0111011000011100, + 0b0011001000001110, + 0b0111011000001110, + 0b0001000101111000, + 0b1000100010111110, + 0b0011001101111000, + 0b0001000100111100, + 0b0111011101111000, + 0b0011001100111100, + 0b0001000100011110, + 0b0111011100111100, + 0b0011001100011110, + 0b0111011100011110, + 0b0001000110111110, + 0b0011001110111110, + 0b0111011110111110, + 0b0111001011000000, + 0b1011100101110000, + 0b1101110010111100, + 0b0111001001100000, + 0b1011100100111000, + 0b1101110010011110, + 0b0111001000110000, + 0b1011100100011100, + 0b0111001000011000, + 0b1011100100001110, + 0b0111001000001100, + 0b0111001000000110, + 0b0011000101110000, + 0b1001100010111100, + 0b0111001101110000, + 0b0011000100111000, + 0b1001100010011110, + 0b0111001100111000, + 0b1011100110011110, + 0b0111001100011100, + 0b0011000100001110, + 0b0111001100001110, + 0b0001000010111100, + 0b0011000110111100, + 0b0001000010011110, + 0b0111001110111100, + 0b0011000110011110, + 0b0111001110011110, + 0b0111000101100000, + 0b1011100010111000, + 0b1101110001011110, + 0b0111000100110000, + 0b1011100010011100, + 0b0111000100011000, + 0b1011100010001110, + 0b0111000100001100, + 0b0111000100000110, + 0b0011000010111000, + 0b1001100001011110, + 0b0111000110111000, + 0b0011000010011100, + 0b0111000110011100, + 0b0011000010001110, + 0b0111000110001110, + 0b0001000001011110, + 0b0011000011011110, + 0b0111000111011110, + 0b0111000010110000, + 0b1011100001011100, + 0b0111000010011000, + 0b1011100001001110, + 0b0111000010001100, + 0b0111000010000110, + 0b0011000001011100, + 0b0111000011011100, + 0b0011000001001110, + 0b0111000011001110, + 0b0111000001011000, + 0b1011100000101110, + 0b0111000001001100, + 0b0111000001000110, + 0b0011000000101110, + 0b0111000001101110, + 0b0111000000101100, + 0b0111000000100110, + 0b0000101011110000, + 0b1000010101111100, + 0b0000101001111000, + 0b1000010100111110, + 0b0000101000111100, + 0b0000101000011110, + 0b0000101101111100, + 0b0000101100111110, + 0b1111000010111010, + 0b1110000101111010, + 0b1100001011111010, + 0b1000010111111010, + 0b0001101011100000, + 0b1000110101111000, + 0b1100011010111110, + 0b0001101001110000, + 0b1000110100111100, + 0b0001101000111000, + 0b1000110100011110, + 0b0001101000011100, + 0b0001101000001110, + 0b0000100101111000, + 0b1000010010111110, + 0b0001101101111000, + 0b0000100100111100, + 0b0001101100111100, + 0b0000100100011110, + 0b0001101100011110, + 0b0000100110111110, + 0b0001101110111110, + 0b0011101011000000, + 0b1001110101110000, + 0b1100111010111100, + 0b0011101001100000, + 0b1001110100111000, + 0b1100111010011110, + 0b0011101000110000, + 0b1001110100011100, + 0b0011101000011000, + 0b1001110100001110, + 0b0011101000001100, + 0b0011101000000110, + 0b0001100101110000, + 0b1000110010111100, + 0b0011101101110000, + 0b0001100100111000, + 0b1000110010011110, + 0b0011101100111000, + 0b0001100100011100, + 0b0011101100011100, + 0b0001100100001110, + 0b0011101100001110, + 0b0000100010111100, + 0b0001100110111100, + 0b0000100010011110, + 0b0011101110111100, + 0b0001100110011110, + 0b0011101110011110, + 0b1011110101100000, + 0b1101111010111000, + 0b1110111101011110, + 0b0111101001000000, + 0b1011110100110000, + 0b1101111010011100, + 0b0111101000100000, + 0b1011110100011000, + 0b1101111010001110, + 0b0111101000010000, + 0b1011110100001100, + 0b0111101000001000, + 0b1011110100000110, + 0b0111101000000100, + 0b0011100101100000, + 0b1001110010111000, + 0b1100111001011110, + 0b0111101101100000, + 0b0011100100110000, + 0b1001110010011100, + 0b0111101100110000, + 0b1011110110011100, + 0b1001110010001110, + 0b0111101100011000, + 0b0011100100001100, + 0b0111101100001100, + 0b0011100100000110, + 0b0111101100000110, + 0b0001100010111000, + 0b1000110001011110, + 0b0011100110111000, + 0b0001100010011100, + 0b0111101110111000, + 0b0011100110011100, + 0b0001100010001110, + 0b0111101110011100, + 0b0011100110001110, + 0b0111101110001110, + 0b0000100001011110, + 0b0001100011011110, + 0b0011100111011110, + 0b0111101111011110, + 0b0111100101000000, + 0b1011110010110000, + 0b1101111001011100, + 0b0111100100100000, + 0b1011110010011000, + 0b1101111001001110, + 0b0111100100010000, + 0b1011110010001100, + 0b0111100100001000, + 0b1011110010000110, + 0b0111100100000100, + 0b0111100100000010, + 0b0011100010110000, + 0b1001110001011100, + 0b0111100110110000, + 0b0011100010011000, + 0b1001110001001110, + 0b0111100110011000, + 0b1011110011001110, + 0b0111100110001100, + 0b0011100010000110, + 0b0111100110000110, + 0b0001100001011100, + 0b0011100011011100, + 0b0001100001001110, + 0b0111100111011100, + 0b0011100011001110, + 0b0111100111001110, + 0b0111100010100000, + 0b1011110001011000, + 0b1101111000101110, + 0b0111100010010000, + 0b1011110001001100, + 0b0111100010001000, + 0b1011110001000110, + 0b0111100010000100, + 0b0111100010000010, + 0b0011100001011000, + 0b1001110000101110, + 0b0111100011011000, + 0b0011100001001100, + 0b0111100011001100, + 0b0011100001000110, + 0b0111100011000110, + 0b0001100000101110, + 0b0011100001101110, + 0b0111100011101110, + 0b0111100001010000, + 0b1011110000101100, + 0b0111100001001000, + 0b1011110000100110, + 0b0111100001000100, + 0b0111100001000010, + 0b0011100000101100, + 0b0111100001101100, + 0b0011100000100110, + 0b0111100001100110, + 0b0111100000101000, + 0b1011110000010110, + 0b0111100000100100, + 0b0111100000100010, + 0b0011100000010110, + 0b0111100000110110, + 0b0000010101111000, + 0b1000001010111110, + 0b0000010100111100, + 0b0000010100011110, + 0b0000010110111110, + 0b0000110101110000, + 0b1000011010111100, + 0b0000110100111000, + 0b1000011010011110, + 0b0000110100011100, + 0b0000110100001110, + 0b0000010010111100, + 0b0000110110111100, + 0b0000010010011110, + 0b0000110110011110, + 0b0001110101100000, + 0b1000111010111000, + 0b1100011101011110, + 0b0001110100110000, + 0b1000111010011100, + 0b0001110100011000, + 0b1000111010001110, + 0b0001110100001100, + 0b0001110100000110, + 0b0000110010111000, + 0b1000011001011110, + 0b0001110110111000, + 0b0000110010011100, + 0b0001110110011100, + 0b0000110010001110, + 0b0001110110001110, + 0b0000010001011110, + 0b0000110011011110, + 0b0001110111011110, + 0b0011110101000000, + 0b1001111010110000, + 0b1100111101011100, + 0b0011110100100000, + 0b1001111010011000, + 0b1100111101001110, + 0b0011110100010000, + 0b1001111010001100, + 0b0011110100001000, + 0b1001111010000110, + 0b0011110100000100, + 0b0011110100000010, + 0b0001110010110000, + 0b1000111001011100, + 0b0011110110110000, + 0b0001110010011000, + 0b1000111001001110, + 0b0011110110011000, + 0b1001111011001110, + 0b0011110110001100, + 0b0001110010000110, + 0b0011110110000110, + 0b0000110001011100, + 0b0001110011011100, + 0b0000110001001110, + 0b0011110111011100, + 0b0001110011001110, + 0b0011110111001110, + 0b1011111010100000, + 0b1101111101011000, + 0b1110111110101110, + 0b1011111010010000, + 0b1101111101001100, + 0b1011111010001000, + 0b1101111101000110, + 0b1011111010000100, + 0b1011111010000010, + 0b0011110010100000, + 0b1001111001011000, + 0b1100111100101110, + 0b0111110110100000, + 0b0011110010010000, + 0b1001111001001100, + 0b0111110110010000, + 0b1011111011001100, + 0b1001111001000110, + 0b0111110110001000, + 0b0011110010000100, + 0b0111110110000100, + 0b0011110010000010, + 0b0111110110000010, + 0b0001110001011000, + 0b1000111000101110, + 0b0011110011011000, + 0b0001110001001100, + 0b0111110111011000, + 0b0011110011001100, + 0b0001110001000110, + 0b0111110111001100, + 0b0011110011000110, + 0b0111110111000110, + 0b0000110000101110, + 0b0001110001101110, + 0b0011110011101110, + 0b0111110111101110, + 0b1011111001010000, + 0b1101111100101100, + 0b1011111001001000, + 0b1101111100100110, + 0b1011111001000100, + 0b1011111001000010, + 0b0011110001010000, + 0b1001111000101100, + 0b0111110011010000, + 0b0011110001001000, + 0b1001111000100110, + 0b0111110011001000, + 0b1011111001100110, + 0b0111110011000100, + 0b0011110001000010, + 0b0111110011000010, + 0b0001110000101100, + 0b0011110001101100, + 0b0001110000100110, + 0b0111110011101100, + 0b0011110001100110, + 0b0111110011100110, + 0b1011111000101000, + 0b1101111100010110, + 0b1011111000100100, + 0b1011111000100010, + 0b0011110000101000, + 0b1001111000010110, + 0b0111110001101000, + 0b0011110000100100, + 0b0111110001100100, + 0b0011110000100010, + 0b0111110001100010, + 0b0001110000010110, + 0b0011110000110110, + 0b0111110001110110, + 0b1011111000010100, + 0b1011111000010010, + 0b0011110000010100, + 0b0111110000110100, + 0b0011110000010010, + 0b0111110000110010, + 0b0000001010111100, + 0b0000001010011110, + 0b0000011010111000, + 0b1000001101011110, + 0b0000011010011100, + 0b0000011010001110, + 0b0000001001011110, + 0b0000011011011110, + 0b0000111010110000, + 0b1000011101011100, + 0b0000111010011000, + 0b1000011101001110, + 0b0000111010001100, + 0b0000111010000110, + 0b0000011001011100, + 0b0000111011011100, + 0b0000011001001110, + 0b0000111011001110, + 0b0001111010100000, + 0b1000111101011000, + 0b1100011110101110, + 0b0001111010010000, + 0b1000111101001100, + 0b0001111010001000, + 0b1000111101000110, + 0b0001111010000100, + 0b0001111010000010, + 0b0000111001011000, + 0b1000011100101110, + 0b0001111011011000, + 0b1000111101101110, + 0b0001111011001100, + 0b0000111001000110, + 0b0001111011000110, + 0b0000011000101110, + 0b0000111001101110, + 0b0001111011101110, + 0b1001111101010000, + 0b1100111110101100, + 0b1001111101001000, + 0b1100111110100110, + 0b1001111101000100, + 0b1001111101000010, + 0b0001111001010000, + 0b1000111100101100, + 0b0011111011010000, + 0b1001111101101100, + 0b1000111100100110, + 0b0011111011001000, + 0b0001111001000100, + 0b0011111011000100, + 0b0001111001000010, + 0b0011111011000010, + 0b0000111000101100, + 0b0001111001101100, + 0b0000111000100110, + 0b0011111011101100, + 0b0001111001100110, + 0b0011111011100110, + 0b1101111110101000, + 0b1110111111010110, + 0b1101111110100100, + 0b1101111110100010, + 0b1001111100101000, + 0b1100111110010110, + 0b1011111101101000, + 0b1001111100100100, + 0b1011111101100100, + 0b1001111100100010, + 0b1011111101100010, + 0b0001111000101000, + 0b1000111100010110, + 0b0011111001101000, + 0b0001111000100100, + 0b0111111011101000, + 0b0011111001100100, + 0b0001111000100010, + 0b0111111011100100, + 0b0011111001100010, + 0b0111111011100010, + 0b0000111000010110, + 0b0001111000110110, + 0b0011111001110110, + 0b0111111011110110, + 0b1101111110010100, + 0b1101111110010010, + 0b1001111100010100, + 0b1011111100110100, + 0b1001111100010010, + 0b1011111100110010, + 0b0001111000010100, + 0b0011111000110100, + 0b0001111000010010, + 0b0111111001110100, + 0b0011111000110010, + 0b0111111001110010, + 0b1101111110001010, + 0b1001111100001010, + 0b1011111100011010, + 0b0001111000001010, + 0b0011111000011010, + 0b0111111000111010, + 0b0000001101011100, + 0b0000001101001110, + 0b0000011101011000, + 0b1000001110101110, + 0b0000011101001100, + 0b0000011101000110, + 0b0000001100101110, + 0b0000011101101110, + 0b0000111101010000, + 0b1000011110101100, + 0b0000111101001000, + 0b1000011110100110, + 0b0000111101000100, + 0b0000111101000010, + 0b0000011100101100, + 0b0000111101101100, + 0b0000011100100110, + 0b0000111101100110, + 0b1000111110101000, + 0b1100011111010110, + 0b1000111110100100, + 0b1000111110100010, + 0b0000111100101000, + 0b1000011110010110, + 0b0001111101101000, + 0b1000111110110110, + 0b0001111101100100, + 0b0000111100100010, + 0b0001111101100010, + 0b0000011100010110, + 0b0000111100110110, + 0b0001111101110110, + 0b1100111111010100, + 0b1100111111010010, + 0b1000111110010100, + 0b1001111110110100, + 0b1000111110010010, + 0b1001111110110010, + 0b0000111100010100, + 0b0001111100110100, + 0b0000111100010010, + 0b0011111101110100, + 0b0001111100110010, + 0b0011111101110010, + 0b1100111111001010, + 0b1000111110001010, + 0b1001111110011010, + 0b0000111100001010, + 0b0001111100011010, + 0b0011111100111010, + 0b0000001110101100, + 0b0000001110100110, + 0b0000011110101000, + 0b1000001111010110, + 0b0000011110100100, + 0b0000011110100010, + 0b0000001110010110, + 0b0000011110110110, + 0b1000011111010100, + 0b1000011111010010, + 0b0000011110010100, + 0b0000111110110100, + 0b0000011110010010, + 0b0000111110110010, + 0b1100011111101010, +]; + +pub const ECC_MICRO: [u16; 344] = [ + /* k = 7 */ + 76, 925, 537, 597, 784, 691, 437, + + /* k = 8 */ + 237, 308, 436, 284, 646, 653, 428, 379, + + /* k = 9 */ + 567, 527, 622, 257, 289, 362, 501, 441, 205, + + /* k = 10 */ + 377, 457, 64, 244, 826, 841, 818, 691, 266, 612, + + /* k = 11 */ + 462, 45, 565, 708, 825, 213, 15, 68, 327, 602, 904, + + /* k = 12 */ + 597, 864, 757, 201, 646, 684, 347, 127, 388, 7, 69, 851, + + /* k = 13 */ + 764, 713, 342, 384, 606, 583, 322, 592, 678, 204, 184, 394, 692, + + /* k = 14 */ + 669, 677, 154, 187, 241, 286, 274, 354, 478, 915, 691, 833, 105, 215, + + /* k = 15 */ + 460, 829, 476, 109, 904, 664, 230, 5, 80, 74, 550, 575, 147, 868, 642, + + /* k = 16 */ + 274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65, + + /* k = 18 */ + 279, 577, 315, 624, 37, 855, 275, 739, 120, 297, 312, 202, 560, 321, 233, 756, + 760, 573, + + /* k = 21 */ + 108, 519, 781, 534, 129, 425, 681, 553, 422, 716, 763, 693, 624, 610, 310, 691, + 347, 165, 193, 259, 568, + + /* k = 26 */ + 443, 284, 887, 544, 788, 93, 477, 760, 331, 608, 269, 121, 159, 830, 446, 893, + 699, 245, 441, 454, 325, 858, 131, 847, 764, 169, + + /* k = 32 */ + 361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517, + 273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410, + + /* k = 38 */ + 234, 228, 438, 848, 133, 703, 529, 721, 788, 322, 280, 159, 738, 586, 388, 684, + 445, 680, 245, 595, 614, 233, 812, 32, 284, 658, 745, 229, 95, 689, 920, 771, + 554, 289, 231, 125, 117, 518, + + /* k = 44 */ + 476, 36, 659, 848, 678, 64, 764, 840, 157, 915, 470, 876, 109, 25, 632, 405, + 417, 436, 714, 60, 376, 97, 413, 706, 446, 21, 3, 773, 569, 267, 272, 213, + 31, 560, 231, 758, 103, 271, 572, 436, 339, 730, 82, 285, + + /* k = 50 */ + 923, 797, 576, 875, 156, 706, 63, 81, 257, 874, 411, 416, 778, 50, 205, 303, + 188, 535, 909, 155, 637, 230, 534, 96, 575, 102, 264, 233, 919, 593, 865, 26, + 579, 623, 766, 146, 10, 739, 246, 127, 71, 244, 211, 477, 920, 876, 427, 820, + 718, 435 +]; + +pub const M_PDF417_VARIANTS_COUNT: usize = 34; +pub const M_PDF417_VARIANTS: [u16; M_PDF417_VARIANTS_COUNT * 4] = [ + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* cols */ + 11, 14, 17, 20, 24, 28, 8, 11, 14, 17, 20, 23, 26, 6, 8, 10, 12, 15, 20, 26, 32, 38, 44, 4, 6, 8, 10, 12, 15, 20, 26, 32, 38, 44, /* rows */ + 7, 7, 7, 8, 8, 8, 8, 9, 9, 10, 11, 13, 15, 12, 14, 16, 18, 21, 26, 32, 38, 44, 50, 8, 12, 14, 16, 18, 21, 26, 32, 38, 44, 50, /* ecc count */ + 0, 0, 0, 7, 7, 7, 7, 15, 15, 24, 34, 57, 84, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294, 7, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294 /* k-offset */ +]; + +pub const M_PDF417_RAP: [u8; M_PDF417_VARIANTS_COUNT * 4] = [ + 1, 8, 36, 19, 9, 25, 1, 1, 8, 36, 19, 9, 27, 1, 7, 15, 25, 37, 1, 1, 21, 15, 1, 47, 1, 7, 15, 25, 37, 1, 1, 21, 15, 1, /* left idx */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 15, 25, 37, 17, 9, 29, 31, 25, 19, 1, 7, 15, 25, 37, 17, 9, 29, 31, 25, /* center idx */ + 9, 8, 36, 19, 17, 33, 1, 9, 8, 36, 19, 17, 35, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, 43, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, /* right idx */ + 0, 1, 2, 0, 2, 0, 0, 0, 1, 2, 0, 2, 2, 0, 0, 2, 0, 0, 0, 0, 2, 2, 0, 1, 0, 0, 2, 0, 0, 0, 0, 2, 2, 0 /* initial table */ +]; + +pub const M_PDF417_SIDE: [u16; 52] = [ + 0x322, 0x3A2, 0x3B2, 0x332, 0x372, 0x37A, 0x33A, 0x3BA, 0x39A, 0x3DA, + 0x3CA, 0x38A, 0x30A, 0x31A, 0x312, 0x392, 0x3D2, 0x3D6, 0x3D4, 0x394, + 0x3B4, 0x3A4, 0x3A6, 0x3AE, 0x3AC, 0x3A8, 0x328, 0x32C, 0x32E, 0x326, + 0x336, 0x3B6, 0x396, 0x316, 0x314, 0x334, 0x374, 0x364, 0x366, 0x36E, + 0x36C, 0x368, 0x348, 0x358, 0x35C, 0x35E, 0x34E, 0x34C, 0x344, 0x346, + 0x342, 0x362 +]; + +pub const M_PDF417_CENTER: [u16; 52] = [ + 0x2CE, 0x24E, 0x26E, 0x22E, 0x226, 0x236, 0x216, 0x212, 0x21A, 0x23A, + 0x232, 0x222, 0x262, 0x272, 0x27A, 0x2FA, 0x2F2, 0x2F6, 0x276, 0x274, + 0x264, 0x266, 0x246, 0x242, 0x2C2, 0x2E2, 0x2E6, 0x2E4, 0x2EC, 0x26C, + 0x22C, 0x228, 0x268, 0x2E8, 0x2C8, 0x2CC, 0x2C4, 0x2C6, 0x286, 0x28E, + 0x28C, 0x29C, 0x298, 0x2B8, 0x2B0, 0x290, 0x2D0, 0x250, 0x258, 0x25C, + 0x2DC, 0x2DE +]; + +/// Get the variant number for a dimension (rows, cols). Returns None the +/// combinaison of rows and cols is invalid (not supported) according to the +/// MicroPDF417 specification. +pub const fn get_variant(rows: u8, cols: u8) -> Option { + let mut start = 0; + while start < M_PDF417_VARIANTS_COUNT && M_PDF417_VARIANTS[start] != cols as u16 { + start += 1; + } + + if start == M_PDF417_VARIANTS_COUNT { + return None; // the number of cols is invalid + } + + let mut end = start; + while end < M_PDF417_VARIANTS_COUNT && M_PDF417_VARIANTS[end] == cols as u16 { + end += 1; + } + + while start < end && M_PDF417_VARIANTS[1 * M_PDF417_VARIANTS_COUNT + start] != rows as u16 { + start += 1; + } + + if start == end { + return None; // the number of rows is invalid + } + + Some(start as u8) +} + +/// Find a suitable variant that has at least `capacity` free codeword slots. +/// None if capacity is too large to be stored by a MicroPDF417. +pub const fn find_variant(capacity: usize) -> Option { + let mut i = 0; + + while i < M_PDF417_VARIANTS_COUNT { + if M_PDF417_VARIANTS[0 * M_PDF417_VARIANTS_COUNT + i] as usize + * M_PDF417_VARIANTS[1 * M_PDF417_VARIANTS_COUNT + i] as usize + - M_PDF417_VARIANTS[2 * M_PDF417_VARIANTS_COUNT + i] as usize >= capacity { + break; + } + i += 1; + } + + if i == M_PDF417_VARIANTS_COUNT { + None + } else { + Some(i as u8) + } +} + +/// Returns the dimensions of a MicroPDF417 variant as (rows, cols). +#[inline] +pub const fn variant_dim(variant: u8) -> (u8, u8) { + assert!(variant <= 34, "invalid variant (0-34)"); + ( + M_PDF417_VARIANTS[0 * M_PDF417_VARIANTS_COUNT + variant as usize] as u8, + M_PDF417_VARIANTS[1 * M_PDF417_VARIANTS_COUNT + variant as usize] as u8 + ) +}