#![forbid(unsafe_code)]
use crate::common::{
buffer::SliceWriter, errors::EncodeError, traits::BarcodeEncoder, types::Encoded,
};
use super::pdf417_table::CODEWORD_TABLE;
const START_PATTERN: u32 = 0x1fea8;
const STOP_PATTERN: u32 = 0x3fa29;
const PRIME: u32 = 929;
const MAX_CW: usize = 929;
const MAX_EC: usize = 64;
const MAX_COLS: usize = 30;
const MAX_ROWS: usize = 90;
const ROW_HEIGHT: usize = 3;
const EC_L0: [u16; 2] = [27, 917];
const EC_L1: [u16; 4] = [522, 568, 723, 809];
const EC_L2: [u16; 8] = [237, 308, 436, 284, 646, 653, 428, 379];
const EC_L3: [u16; 16] = [
274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65,
];
const EC_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,
];
const EC_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,
];
fn ec_coefficients(level: usize) -> &'static [u16] {
match level {
0 => &EC_L0,
1 => &EC_L1,
2 => &EC_L2,
3 => &EC_L3,
4 => &EC_L4,
_ => &EC_L5,
}
}
fn generate_ec(data: &[u16], level: usize, out: &mut [u16]) {
let coeff = ec_coefficients(level);
let k = coeff.len();
let mut e = [0u16; MAX_EC];
for &cwv in data {
let t1 = (cwv as u32 + e[k - 1] as u32) % PRIME;
let mut j = k - 1;
while j >= 1 {
let t2 = (t1 * coeff[j] as u32) % PRIME;
let t3 = PRIME - t2;
e[j] = ((e[j - 1] as u32 + t3) % PRIME) as u16;
j -= 1;
}
let t2 = (t1 * coeff[0] as u32) % PRIME;
e[0] = ((PRIME - t2) % PRIME) as u16;
}
for (idx, slot) in out[..k].iter_mut().enumerate() {
let v = e[k - 1 - idx];
*slot = if v != 0 { PRIME as u16 - v } else { 0 };
}
}
fn byte_compaction(data: &[u8], cw: &mut [u16], mut n: usize) -> Result<usize, EncodeError> {
let len = data.len();
let put = |cw: &mut [u16], n: &mut usize, v: u16| -> Result<(), EncodeError> {
*cw.get_mut(*n).ok_or(EncodeError::DataTooLong)? = v;
*n += 1;
Ok(())
};
put(cw, &mut n, if len.is_multiple_of(6) { 924 } else { 901 })?;
let mut i = 0;
while i + 6 <= len {
let mut t: u64 = 0;
for j in 0..6 {
t = (t << 8) | data[i + j] as u64;
}
let mut tmp = [0u16; 5];
for k in (0..5).rev() {
tmp[k] = (t % 900) as u16;
t /= 900;
}
for &v in &tmp {
put(cw, &mut n, v)?;
}
i += 6;
}
while i < len {
put(cw, &mut n, data[i] as u16)?;
i += 1;
}
Ok(n)
}
fn isqrt(n: usize) -> usize {
if n == 0 {
return 0;
}
let mut x = n;
let mut y = x.div_ceil(2);
while y < x {
x = y;
y = (x + n / x) / 2;
}
x
}
fn recommended_level(data_cw: usize) -> usize {
match data_cw {
0..=40 => 2,
41..=160 => 3,
161..=320 => 4,
_ => 5,
}
}
fn dimensions(total: usize) -> Result<(usize, usize), EncodeError> {
let start = isqrt(total).clamp(1, MAX_COLS);
for c in start..=MAX_COLS {
let r = total.div_ceil(c);
if (3..=MAX_ROWS).contains(&r) {
return Ok((r, c));
}
}
for c in (1..start).rev() {
let r = total.div_ceil(c);
if (3..=MAX_ROWS).contains(&r) {
return Ok((r, c));
}
}
Err(EncodeError::DataTooLong)
}
fn row_indicators(y: usize, r: usize, c: usize, level: usize, cluster: usize) -> (usize, usize) {
let base = 30 * (y / 3);
match cluster {
0 => (base + (r - 1) / 3, base + (c - 1)),
1 => (base + level * 3 + (r - 1) % 3, base + (r - 1) / 3),
_ => (base + (c - 1), base + level * 3 + (r - 1) % 3),
}
}
fn row_width(cols: usize) -> usize {
17 * (cols + 3) + 18
}
pub struct Pdf417;
impl BarcodeEncoder for Pdf417 {
type Input = str;
fn encode_into(input: &str, buf: &mut [bool]) -> Result<Encoded, EncodeError> {
if input.is_empty() {
return Err(EncodeError::InvalidInput("PDF417 input must not be empty"));
}
let mut cw = [0u16; MAX_CW];
let payload_end = byte_compaction(input.as_bytes(), &mut cw, 1)?;
let level = recommended_level(payload_end);
let ec = 1usize << (level + 1);
let (rows, cols) = dimensions(payload_end + ec)?;
let capacity = rows * cols;
let data_len = capacity - ec;
if data_len > MAX_CW - MAX_EC || payload_end > data_len {
return Err(EncodeError::DataTooLong);
}
for slot in cw[payload_end..data_len].iter_mut() {
*slot = 900;
}
cw[0] = data_len as u16;
let (data_part, ec_part) = cw.split_at_mut(data_len);
generate_ec(data_part, level, ec_part);
let width = row_width(cols);
let height = rows * ROW_HEIGHT;
if buf.len() < height * width {
return Err(EncodeError::BufferTooSmall);
}
let mut w = SliceWriter::new(buf);
for y in 0..rows {
let cluster = y % 3;
let (left, right) = row_indicators(y, rows, cols, level, cluster);
for _ in 0..ROW_HEIGHT {
append_pattern(&mut w, START_PATTERN, 17)?;
append_pattern(&mut w, CODEWORD_TABLE[cluster][left], 17)?;
for x in 0..cols {
let value = cw[y * cols + x] as usize;
append_pattern(&mut w, CODEWORD_TABLE[cluster][value], 17)?;
}
append_pattern(&mut w, CODEWORD_TABLE[cluster][right], 17)?;
append_pattern(&mut w, STOP_PATTERN, 18)?;
}
}
Ok(Encoded::Matrix { width, height })
}
fn symbology_name() -> &'static str {
"PDF417"
}
}
fn append_pattern(w: &mut SliceWriter, pattern: u32, len: u32) -> Result<(), EncodeError> {
for i in (0..len).rev() {
w.push((pattern >> i) & 1 == 1)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn encode(input: &str, buf: &mut [bool]) -> (usize, usize) {
match Pdf417::encode_into(input, buf).unwrap() {
Encoded::Matrix { width, height } => (width, height),
_ => panic!("expected matrix"),
}
}
#[test]
fn test_encode_basic() {
let mut buf = [false; 1 << 16];
let (w, h) = encode("PDF417 test", &mut buf);
assert!(h >= 3 && w > 0);
}
#[test]
fn test_empty_input() {
let mut buf = [false; 1 << 16];
assert!(Pdf417::encode_into("", &mut buf).is_err());
}
#[test]
fn test_symbology_name() {
assert_eq!(Pdf417::symbology_name(), "PDF417");
}
#[test]
fn test_ec_known() {
let mut ec = [0u16; MAX_EC];
generate_ec(&[5, 453, 178, 121, 239], 2, &mut ec);
assert!(ec[..8].iter().all(|&v| (v as u32) < PRIME));
}
#[cfg(feature = "alloc")]
#[test]
fn test_svg_output() {
let svg = Pdf417::encode("Test").unwrap().to_svg_string();
assert!(svg.starts_with("<svg "));
}
}