Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
147 lines
4.3 KiB
Rust
147 lines
4.3 KiB
Rust
use crate::model::DecodedBarcode;
|
|
|
|
pub fn extract_barcodes(bytes: &[u8]) -> Vec<DecodedBarcode> {
|
|
extract_from_zpl_bytes(bytes)
|
|
}
|
|
|
|
pub fn extract_from_zpl(input: &str) -> Vec<DecodedBarcode> {
|
|
extract_from_zpl_bytes(input.as_bytes())
|
|
}
|
|
|
|
fn extract_from_zpl_bytes(bytes: &[u8]) -> Vec<DecodedBarcode> {
|
|
let mut index = 0usize;
|
|
let mut pending_format: Option<String> = None;
|
|
let mut barcodes = Vec::new();
|
|
|
|
while index + 2 < bytes.len() {
|
|
if bytes[index] != b'^' && bytes[index] != b'~' {
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
let command_start = index;
|
|
let command = &bytes[index + 1..index + 3];
|
|
index += 3;
|
|
|
|
if let Some(format) = barcode_format(command) {
|
|
pending_format = Some(format.to_string());
|
|
continue;
|
|
}
|
|
|
|
if command == b"FD" {
|
|
let end = find_bytes(&bytes[index..], b"^FS")
|
|
.map(|offset| index + offset)
|
|
.unwrap_or(bytes.len());
|
|
if let Some(format) = pending_format.take() {
|
|
let raw = String::from_utf8_lossy(&bytes[index..end]);
|
|
let value = normalize_field_data(raw.trim());
|
|
if !value.is_empty() {
|
|
barcodes.push(DecodedBarcode { format, value });
|
|
}
|
|
}
|
|
index = end.saturating_add(3);
|
|
continue;
|
|
}
|
|
|
|
if command == b"FS" || command == b"XZ" {
|
|
pending_format = None;
|
|
}
|
|
|
|
if index <= command_start {
|
|
index = command_start + 1;
|
|
}
|
|
}
|
|
|
|
barcodes
|
|
}
|
|
|
|
fn barcode_format(command: &[u8]) -> Option<&'static str> {
|
|
match command {
|
|
b"BC" => Some("CODE_128"),
|
|
b"B3" => Some("CODE_39"),
|
|
b"BE" => Some("EAN_13"),
|
|
b"B8" => Some("EAN_8"),
|
|
b"BU" => Some("UPC_A"),
|
|
b"B9" => Some("UPC_E"),
|
|
b"B2" => Some("ITF"),
|
|
b"BQ" => Some("QR_CODE"),
|
|
b"BX" => Some("DATA_MATRIX"),
|
|
b"B7" => Some("PDF_417"),
|
|
b"BA" => Some("CODE_93"),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
|
|
if needle.is_empty() {
|
|
return Some(0);
|
|
}
|
|
haystack.windows(needle.len()).position(|window| window == needle)
|
|
}
|
|
|
|
fn normalize_field_data(raw: &str) -> String {
|
|
let mut value = raw.to_string();
|
|
|
|
// Zebra Code 128 control sequences commonly select code sets or emit FNC1.
|
|
for prefix in [">;", ">:", ">9"] {
|
|
if value.starts_with(prefix) {
|
|
value = value[prefix.len()..].to_string();
|
|
break;
|
|
}
|
|
}
|
|
value = value.replace(">8", "\u{001d}");
|
|
|
|
// ^FH defaults to '_' as the hex escape character. Decode common escaped bytes.
|
|
let hex = regex::Regex::new(r"_([0-9A-Fa-f]{2})").expect("valid regex");
|
|
hex.replace_all(&value, |caps: ®ex::Captures<'_>| {
|
|
u8::from_str_radix(&caps[1], 16)
|
|
.ok()
|
|
.map(char::from)
|
|
.unwrap_or('_')
|
|
.to_string()
|
|
})
|
|
.into_owned()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn extracts_multiple_zpl_barcode_types() {
|
|
let zpl = "^XA^FO10,10^BCN,80,Y,N,N^FD>;0123456789012^FS^FO10,100^BQN,2,5^FDLA,HELLO^FS^XZ";
|
|
let found = extract_from_zpl(zpl);
|
|
assert_eq!(
|
|
found,
|
|
vec![
|
|
DecodedBarcode { format: "CODE_128".into(), value: "0123456789012".into() },
|
|
DecodedBarcode { format: "QR_CODE".into(), value: "LA,HELLO".into() },
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn preserves_gs1_separator_for_fnc1() {
|
|
let zpl = "^XA^BCN,80,N,N,N^FD>801095011015300310ABC^FS^XZ";
|
|
let found = extract_from_zpl(zpl);
|
|
assert_eq!(found[0].value, "\u{001d}01095011015300310ABC");
|
|
}
|
|
|
|
#[test]
|
|
fn ignores_normal_text_fields() {
|
|
let zpl = "^XA^FO10,10^A0N,30,30^FDNOT-A-BARCODE^FS^XZ";
|
|
assert!(extract_from_zpl(zpl).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn arbitrary_binary_printer_data_never_panics() {
|
|
let payload = [
|
|
0x1b, b'@', 0xff, 0x00, 0xfe, b'^', 0xff, 0xfd, 0x1b, b'(', b'G',
|
|
0x10, 0x00, 0x80, 0x81, 0x82, b'^', b'F', b'D', 0xf0, 0x9f, 0x92,
|
|
0xa9, b'^', b'F', b'S',
|
|
];
|
|
let found = extract_barcodes(&payload);
|
|
assert!(found.is_empty());
|
|
}
|
|
}
|