#![allow(dead_code)]
pub(crate) fn split_composite_input(input: &str) -> Result<(&str, &str), crate::error::Error> {
match input.split_once('|') {
Some((l, c)) if !l.is_empty() && !c.is_empty() => Ok((l, c)),
_ => Err(crate::error::Error::InvalidData(
"composite: input must be 'LINEAR|COMP' (pipe-separated, both non-empty)".into(),
)),
}
}
pub(crate) const DATABAROMNI_SEPPAD: [u8; 4] = [0, 0, 0, 0];
pub(crate) const DATABAROMNI_FINDERSEP: [u8; 13] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0];
pub(crate) const DATABAROMNI_F3PAT: [u8; 13] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1];
pub(crate) fn sbs_to_pixels(sbs: &[u32]) -> Vec<u8> {
let mut out = Vec::new();
let mut is_bar = true;
for &w in sbs {
let v = if is_bar { 1u8 } else { 0u8 };
for _ in 0..w {
out.push(v);
}
is_bar = !is_bar;
}
out
}
pub(crate) fn databaromni_separator(bot: &[u8]) -> Vec<u8> {
let mut sep: Vec<u8> = bot.iter().map(|&b| 1 - b).collect();
let n = sep.len();
for s in sep.iter_mut().take(3) {
*s = 0;
}
for s in sep.iter_mut().skip(n.saturating_sub(4)) {
*s = 0;
}
for fp in [18usize, 64usize] {
apply_sepfinder(bot, &mut sep, fp);
}
sep
}
pub(crate) const CCA_ROWMULT: usize = 2;
pub(crate) const DATABAROMNI_LINHEIGHT: usize = 33;
pub(crate) const DATABARTRUNCATED_LINHEIGHT: usize = 13;
pub(crate) fn build_databaromni_composite(
cc_pixs: &crate::encoding::BitMatrix,
linsbs: &[u32],
linheight: usize,
) -> crate::encoding::BitMatrix {
let bot = sbs_to_pixels(linsbs);
let mut linpixs96: Vec<u8> = Vec::with_capacity(bot.len() + 1);
linpixs96.push(0);
linpixs96.extend_from_slice(&bot);
let sep95 = databaromni_separator(&bot);
let mut sep96: Vec<u8> = Vec::with_capacity(sep95.len() + 1);
sep96.push(0);
sep96.extend_from_slice(&sep95);
let cc_width = cc_pixs.width();
let pixx = cc_width.max(linpixs96.len() + 4);
let cc_rows = cc_pixs.height();
let pixy = cc_rows * CCA_ROWMULT + 1 + linheight;
let mut bm = crate::encoding::BitMatrix::new(pixx, pixy);
for r in 0..cc_rows {
for rep in 0..CCA_ROWMULT {
let y = r * CCA_ROWMULT + rep;
for x in 0..cc_width {
bm.set(x, y, cc_pixs.get(x, r));
}
}
}
let sep_y = cc_rows * CCA_ROWMULT;
for (x, &v) in sep96.iter().enumerate() {
bm.set(4 + x, sep_y, v == 1);
}
for rep in 0..linheight {
let y = cc_rows * CCA_ROWMULT + 1 + rep;
for (x, &v) in linpixs96.iter().enumerate() {
bm.set(4 + x, y, v == 1);
}
}
bm
}
pub(crate) fn encode_databaromni_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let sbs45 = crate::symbology::databar::omni_sbs_with_linkage(linear, true)?;
let linsbs: Vec<u32> = sbs45.iter().map(|&w| u32::from(w)).collect();
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_databar_omni_cca: payload requires CC-B; \
use composite_databar_omni_ccb instead"
.into(),
));
}
let (cc_bm, _metric) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_databaromni_composite(
&cc_bm,
&linsbs,
DATABAROMNI_LINHEIGHT,
))
}
pub(crate) fn encode_databartruncated_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let sbs45 = crate::symbology::databar::omni_sbs_with_linkage(linear, true)?;
let linsbs: Vec<u32> = sbs45.iter().map(|&w| u32::from(w)).collect();
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_databar_truncated_cca: payload requires CC-B; \
use composite_databar_truncated_ccb instead"
.into(),
));
}
let (cc_bm, _metric) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_databaromni_composite(
&cc_bm,
&linsbs,
DATABARTRUNCATED_LINHEIGHT,
))
}
pub(crate) fn encode_databartruncated_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let sbs45 = crate::symbology::databar::omni_sbs_with_linkage(linear, true)?;
let linsbs: Vec<u32> = sbs45.iter().map(|&w| u32::from(w)).collect();
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
let cc_bm = match cc.version {
crate::symbology::gs1_cc::CcVersion::A => {
let (bm, _m) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::B => {
let bytes: Vec<u8> = cc.codewords.iter().map(|&v| v as u8).collect();
let (bm, _m) = crate::symbology::micropdf417::render_ccb(&bytes, 4)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::C => {
return Err(crate::error::Error::InvalidData(
"composite_databar_truncated_ccb: CC-C is only valid with GS1-128, \
not DataBar Truncated"
.into(),
));
}
};
Ok(build_databaromni_composite(
&cc_bm,
&linsbs,
DATABARTRUNCATED_LINHEIGHT,
))
}
pub(crate) const DATABARSTACKED_COMPOSITE_CC_UCOLS: u8 = 2;
pub(crate) const DATABARSTACKED_LINWIDTH: usize = 50;
pub(crate) const DATABARSTACKED_TOP_HEIGHT: usize = 5;
pub(crate) const DATABARSTACKED_BOT_HEIGHT: usize = 7;
pub(crate) fn build_databarstacked_composite(
cc_pixs: &crate::encoding::BitMatrix,
composite_sep_50: &[u8; 50],
stacked_top_50: &[u8; 50],
stacked_sep_50: &[u8; 50],
stacked_bot_50: &[u8; 50],
) -> crate::encoding::BitMatrix {
let ccpixx = cc_pixs.width();
let pixx = ccpixx + 1;
let cc_rows = cc_pixs.height();
let lin_height = DATABARSTACKED_TOP_HEIGHT + 1 + DATABARSTACKED_BOT_HEIGHT;
let pixy = cc_rows * CCA_ROWMULT + 1 + lin_height;
let mut bm = crate::encoding::BitMatrix::new(pixx, pixy);
for r in 0..cc_rows {
for rep in 0..CCA_ROWMULT {
let y = r * CCA_ROWMULT + rep;
for x in 0..ccpixx {
bm.set(x + 1, y, cc_pixs.get(x, r));
}
}
}
let sep_y = cc_rows * CCA_ROWMULT;
for (x, &v) in composite_sep_50.iter().enumerate() {
bm.set(x, sep_y, v == 1);
}
let mut y = sep_y + 1;
for _ in 0..DATABARSTACKED_TOP_HEIGHT {
for (x, &v) in stacked_top_50.iter().enumerate() {
bm.set(x, y, v == 1);
}
y += 1;
}
for (x, &v) in stacked_sep_50.iter().enumerate() {
bm.set(x, y, v == 1);
}
y += 1;
for _ in 0..DATABARSTACKED_BOT_HEIGHT {
for (x, &v) in stacked_bot_50.iter().enumerate() {
bm.set(x, y, v == 1);
}
y += 1;
}
debug_assert_eq!(y, pixy);
bm
}
pub(crate) fn databarstacked_composite_separator(top_50: &[u8; 50]) -> [u8; 50] {
let mut sep = [0u8; 50];
for (i, &v) in top_50.iter().enumerate() {
sep[i] = 1 - v;
}
for s in sep.iter_mut().take(4) {
*s = 0;
}
for s in sep.iter_mut().skip(46) {
*s = 0;
}
apply_sepfinder(top_50, &mut sep, 18);
sep
}
pub(crate) fn encode_databarstacked_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let (widths, csum) = crate::symbology::databar::omni_widths_with_linkage(linear, true)?;
let (top, bot) = crate::symbology::databar::stacked_top_bot(&widths, csum);
let internal_sep = crate::symbology::databar::stacked_sep(&top, &bot);
let composite_sep = databarstacked_composite_separator(&top);
let cc = crate::symbology::gs1_cc::encode_cc(comp, DATABARSTACKED_COMPOSITE_CC_UCOLS)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_databar_stacked_cca: payload requires CC-B; \
use composite_databar_stacked_ccb instead"
.into(),
));
}
let (cc_bm, _metric) =
crate::symbology::micropdf417::render_cca(&cc.codewords, DATABARSTACKED_COMPOSITE_CC_UCOLS)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_databarstacked_composite(
&cc_bm,
&composite_sep,
&top,
&internal_sep,
&bot,
))
}
pub(crate) fn encode_databarstacked_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let (widths, csum) = crate::symbology::databar::omni_widths_with_linkage(linear, true)?;
let (top, bot) = crate::symbology::databar::stacked_top_bot(&widths, csum);
let internal_sep = crate::symbology::databar::stacked_sep(&top, &bot);
let composite_sep = databarstacked_composite_separator(&top);
let cc = crate::symbology::gs1_cc::encode_cc(comp, DATABARSTACKED_COMPOSITE_CC_UCOLS)?;
let cc_bm = match cc.version {
crate::symbology::gs1_cc::CcVersion::A => {
let (bm, _m) = crate::symbology::micropdf417::render_cca(
&cc.codewords,
DATABARSTACKED_COMPOSITE_CC_UCOLS,
)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::B => {
let bytes: Vec<u8> = cc.codewords.iter().map(|&v| v as u8).collect();
let (bm, _m) = crate::symbology::micropdf417::render_ccb(
&bytes,
DATABARSTACKED_COMPOSITE_CC_UCOLS,
)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::C => {
return Err(crate::error::Error::InvalidData(
"composite_databar_stacked_ccb: CC-C is only valid with GS1-128, \
not DataBar Stacked"
.into(),
));
}
};
Ok(build_databarstacked_composite(
&cc_bm,
&composite_sep,
&top,
&internal_sep,
&bot,
))
}
pub(crate) const DATABARSTACKEDOMNI_COMPOSITE_CC_UCOLS: u8 = 2;
pub(crate) fn build_databarstackedomni_composite(
cc_pixs: &crate::encoding::BitMatrix,
composite_sep_50: &[u8; 50],
stacked_top: &[u8; 50],
stacked_sep1: &[u8; 50],
stacked_sep2: &[u8; 50],
stacked_sep3: &[u8; 50],
stacked_bot: &[u8; 50],
) -> crate::encoding::BitMatrix {
let ccpixx = cc_pixs.width();
let pixx = ccpixx + 1;
let cc_rows = cc_pixs.height();
let lin_height = 33 + 1 + 1 + 1 + 33;
let pixy = cc_rows * CCA_ROWMULT + 1 + lin_height;
let mut bm = crate::encoding::BitMatrix::new(pixx, pixy);
for r in 0..cc_rows {
for rep in 0..CCA_ROWMULT {
let y = r * CCA_ROWMULT + rep;
for x in 0..ccpixx {
bm.set(x + 1, y, cc_pixs.get(x, r));
}
}
}
let sep_y = cc_rows * CCA_ROWMULT;
for (x, &v) in composite_sep_50.iter().enumerate() {
bm.set(x, sep_y, v == 1);
}
let mut y = sep_y + 1;
let layers: &[(&[u8; 50], usize)] = &[
(stacked_top, 33),
(stacked_sep1, 1),
(stacked_sep2, 1),
(stacked_sep3, 1),
(stacked_bot, 33),
];
for &(row, mult) in layers {
for _ in 0..mult {
for (x, &v) in row.iter().enumerate() {
bm.set(x, y, v == 1);
}
y += 1;
}
}
debug_assert_eq!(y, pixy);
bm
}
pub(crate) fn encode_databarstackedomni_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let (top, sep1, sep2, sep3, bot) =
crate::symbology::databar::stackedomni_logical_rows(linear, true)?;
let composite_sep = databarstacked_composite_separator(&top);
let cc = crate::symbology::gs1_cc::encode_cc(comp, DATABARSTACKEDOMNI_COMPOSITE_CC_UCOLS)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_databar_stacked_omni_cca: payload requires CC-B; \
use composite_databar_stacked_omni_ccb instead"
.into(),
));
}
let (cc_bm, _metric) = crate::symbology::micropdf417::render_cca(
&cc.codewords,
DATABARSTACKEDOMNI_COMPOSITE_CC_UCOLS,
)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_databarstackedomni_composite(
&cc_bm,
&composite_sep,
&top,
&sep1,
&sep2,
&sep3,
&bot,
))
}
pub(crate) fn encode_databarstackedomni_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let (top, sep1, sep2, sep3, bot) =
crate::symbology::databar::stackedomni_logical_rows(linear, true)?;
let composite_sep = databarstacked_composite_separator(&top);
let cc = crate::symbology::gs1_cc::encode_cc(comp, DATABARSTACKEDOMNI_COMPOSITE_CC_UCOLS)?;
let cc_bm = match cc.version {
crate::symbology::gs1_cc::CcVersion::A => {
let (bm, _m) = crate::symbology::micropdf417::render_cca(
&cc.codewords,
DATABARSTACKEDOMNI_COMPOSITE_CC_UCOLS,
)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::B => {
let bytes: Vec<u8> = cc.codewords.iter().map(|&v| v as u8).collect();
let (bm, _m) = crate::symbology::micropdf417::render_ccb(
&bytes,
DATABARSTACKEDOMNI_COMPOSITE_CC_UCOLS,
)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::C => {
return Err(crate::error::Error::InvalidData(
"composite_databar_stacked_omni_ccb: CC-C is only valid with GS1-128, \
not DataBar Stacked Omni"
.into(),
));
}
};
Ok(build_databarstackedomni_composite(
&cc_bm,
&composite_sep,
&top,
&sep1,
&sep2,
&sep3,
&bot,
))
}
pub(crate) fn databarexpandedstacked_composite_separator(top_row: &[u8]) -> Vec<u8> {
let n = top_row.len();
let mut sep: Vec<u8> = top_row.iter().map(|&b| 1 - b).collect();
for s in sep.iter_mut().take(4) {
*s = 0;
}
for s in sep.iter_mut().skip(n.saturating_sub(4)) {
*s = 0;
}
let mut positions: Vec<usize> = Vec::new();
let mut p = 19usize;
while p + 12 < n {
positions.push(p);
p += 98;
}
let mut p = 70usize;
while p + 12 < n {
positions.push(p);
p += 98;
}
for fp in positions {
apply_sepfinder(top_row, &mut sep, fp);
}
sep
}
pub(crate) fn build_databarexpandedstacked_composite(
cc_pixs: &crate::encoding::BitMatrix,
linear_bm: &crate::encoding::BitMatrix,
composite_sep: &[u8],
) -> crate::encoding::BitMatrix {
let linwidth = linear_bm.width();
let ccpixx = cc_pixs.width();
let cc_rows = cc_pixs.height();
let lin_height = linear_bm.height();
let pixx = linwidth;
let pixy = cc_rows * CCA_ROWMULT + 1 + lin_height;
let mut bm = crate::encoding::BitMatrix::new(pixx, pixy);
let cclpad = (linwidth + 1 - ccpixx) / 2;
for r in 0..cc_rows {
for rep in 0..CCA_ROWMULT {
let y = r * CCA_ROWMULT + rep;
for x in 0..ccpixx {
bm.set(cclpad + x, y, cc_pixs.get(x, r));
}
}
}
let sep_y = cc_rows * CCA_ROWMULT;
for (x, &v) in composite_sep.iter().enumerate() {
bm.set(x, sep_y, v == 1);
}
let lin_y0 = sep_y + 1;
for y in 0..lin_height {
for x in 0..pixx {
bm.set(x, lin_y0 + y, linear_bm.get(x, y));
}
}
bm
}
pub(crate) fn encode_databarexpandedstacked_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let lin_bm = crate::symbology::databar_expanded::encode_stacked(linear, true)?;
let mut top_row: Vec<u8> = Vec::with_capacity(lin_bm.width());
for x in 0..lin_bm.width() {
top_row.push(u8::from(lin_bm.get(x, 0)));
}
let composite_sep = databarexpandedstacked_composite_separator(&top_row);
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_databar_expanded_stacked_cca: payload requires CC-B; \
use composite_databar_expanded_stacked_ccb instead"
.into(),
));
}
let (cc_bm, _) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_databarexpandedstacked_composite(
&cc_bm,
&lin_bm,
&composite_sep,
))
}
pub(crate) fn encode_databarexpandedstacked_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let lin_bm = crate::symbology::databar_expanded::encode_stacked(linear, true)?;
let mut top_row: Vec<u8> = Vec::with_capacity(lin_bm.width());
for x in 0..lin_bm.width() {
top_row.push(u8::from(lin_bm.get(x, 0)));
}
let composite_sep = databarexpandedstacked_composite_separator(&top_row);
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
let cc_bm = match cc.version {
crate::symbology::gs1_cc::CcVersion::A => {
let (bm, _) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::B => {
let bytes: Vec<u8> = cc.codewords.iter().map(|&v| v as u8).collect();
let (bm, _) = crate::symbology::micropdf417::render_ccb(&bytes, 4)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::C => {
return Err(crate::error::Error::InvalidData(
"composite_databar_expanded_stacked_ccb: CC-C is only valid with GS1-128, \
not DataBar Expanded Stacked"
.into(),
));
}
};
Ok(build_databarexpandedstacked_composite(
&cc_bm,
&lin_bm,
&composite_sep,
))
}
pub(crate) const DATABARLIMITED_SEPLEFT: [u8; 3] = [0, 0, 0];
pub(crate) const DATABARLIMITED_SEPRIGHT: [u8; 9] = [0, 0, 0, 0, 0, 0, 0, 0, 0];
pub(crate) const DATABARLIMITED_LINHEIGHT: usize = 10;
pub(crate) fn databarlimited_separator(linsbs: &[u8]) -> Vec<u8> {
let total: usize = linsbs.iter().map(|&w| w as usize).sum();
let mut sep = Vec::with_capacity(total);
let mut bit = 0u8;
for &w in linsbs {
for _ in 0..w {
sep.push(bit);
}
bit ^= 1;
}
for s in sep.iter_mut().take(DATABARLIMITED_SEPLEFT.len()) {
*s = 0;
}
let n = sep.len();
for s in sep.iter_mut().skip(n - DATABARLIMITED_SEPRIGHT.len()) {
*s = 0;
}
let visible_lin = linsbs[..45].iter().map(|&w| w as usize).sum::<usize>();
sep.truncate(visible_lin);
let mut final_sep = Vec::with_capacity(visible_lin + 1);
final_sep.push(0);
final_sep.extend_from_slice(&sep);
final_sep
}
fn databarlimited_linpixs(linsbs: &[u8]) -> Vec<u8> {
let visible = &linsbs[..45];
let sum: usize = visible.iter().map(|&w| w as usize).sum();
let mut linpixs = Vec::with_capacity(sum + 1);
linpixs.push(0);
let mut bit = 1u8;
for &w in visible {
for _ in 0..w {
linpixs.push(bit);
}
bit ^= 1;
}
linpixs
}
pub(crate) fn build_databarlimited_cca_composite(
cc_pixs: &crate::encoding::BitMatrix,
linsbs: &[u8; 46],
linheight: usize,
) -> crate::encoding::BitMatrix {
let sep = databarlimited_separator(linsbs);
let linpixs = databarlimited_linpixs(linsbs);
debug_assert_eq!(sep.len(), linpixs.len());
let cc_width = cc_pixs.width();
debug_assert_eq!(cc_width, 72, "DataBar Limited CC-A expects ccpixx=72");
let pixx = linpixs.len();
let cc_rows = cc_pixs.height();
let pixy = cc_rows * CCA_ROWMULT + 1 + linheight;
let mut bm = crate::encoding::BitMatrix::new(pixx, pixy);
for r in 0..cc_rows {
for rep in 0..CCA_ROWMULT {
let y = r * CCA_ROWMULT + rep;
for x in 0..cc_width {
bm.set(1 + x, y, cc_pixs.get(x, r));
}
}
}
let sep_y = cc_rows * CCA_ROWMULT;
for (x, &v) in sep.iter().enumerate() {
bm.set(x, sep_y, v == 1);
}
for rep in 0..linheight {
let y = cc_rows * CCA_ROWMULT + 1 + rep;
for (x, &v) in linpixs.iter().enumerate() {
bm.set(x, y, v == 1);
}
}
bm
}
pub(crate) fn encode_databarlimited_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let linsbs = crate::symbology::databar::limited_sbs_with_linkage(linear, true)?;
let cc = crate::symbology::gs1_cc::encode_cc(comp, 3)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_databar_limited_cca: payload requires CC-B; \
use composite_databar_limited_ccb instead"
.into(),
));
}
let (cc_bm, _metric) = crate::symbology::micropdf417::render_cca(&cc.codewords, 3)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_databarlimited_cca_composite(
&cc_bm,
&linsbs,
DATABARLIMITED_LINHEIGHT,
))
}
pub(crate) fn build_databarlimited_ccb_composite(
cc_pixs: &crate::encoding::BitMatrix,
linsbs: &[u8; 46],
linheight: usize,
) -> crate::encoding::BitMatrix {
let sep = databarlimited_separator(linsbs);
let linpixs = databarlimited_linpixs(linsbs);
debug_assert_eq!(sep.len(), linpixs.len());
let cc_width = cc_pixs.width();
let pixx = cc_width + 1;
let cc_rows = cc_pixs.height();
let pixy = cc_rows * CCA_ROWMULT + 1 + linheight;
let mut bm = crate::encoding::BitMatrix::new(pixx, pixy);
for r in 0..cc_rows {
for rep in 0..CCA_ROWMULT {
let y = r * CCA_ROWMULT + rep;
for x in 0..cc_width {
bm.set(x, y, cc_pixs.get(x, r));
}
}
}
let sep_y = cc_rows * CCA_ROWMULT;
for (x, &v) in sep.iter().enumerate() {
bm.set(9 + x, sep_y, v == 1);
}
for rep in 0..linheight {
let y = cc_rows * CCA_ROWMULT + 1 + rep;
for (x, &v) in linpixs.iter().enumerate() {
bm.set(9 + x, y, v == 1);
}
}
bm
}
pub(crate) fn encode_databarlimited_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let linsbs = crate::symbology::databar::limited_sbs_with_linkage(linear, true)?;
let cc = crate::symbology::gs1_cc::encode_cc(comp, 3)?;
match cc.version {
crate::symbology::gs1_cc::CcVersion::A => {
let (cc_bm, _m) = crate::symbology::micropdf417::render_cca(&cc.codewords, 3)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_databarlimited_cca_composite(
&cc_bm,
&linsbs,
DATABARLIMITED_LINHEIGHT,
))
}
crate::symbology::gs1_cc::CcVersion::B => {
let bytes: Vec<u8> = cc.codewords.iter().map(|&v| v as u8).collect();
let (cc_bm, _m) = crate::symbology::micropdf417::render_ccb(&bytes, 3)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_databarlimited_ccb_composite(
&cc_bm,
&linsbs,
DATABARLIMITED_LINHEIGHT,
))
}
crate::symbology::gs1_cc::CcVersion::C => Err(crate::error::Error::InvalidData(
"composite_databar_limited_ccb: CC-C is only valid with GS1-128, \
not DataBar Limited"
.into(),
)),
}
}
pub(crate) fn encode_databaromni_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let sbs45 = crate::symbology::databar::omni_sbs_with_linkage(linear, true)?;
let linsbs: Vec<u32> = sbs45.iter().map(|&w| u32::from(w)).collect();
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
let cc_bm = match cc.version {
crate::symbology::gs1_cc::CcVersion::A => {
let (bm, _m) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::B => {
let bytes: Vec<u8> = cc.codewords.iter().map(|&v| v as u8).collect();
let (bm, _m) = crate::symbology::micropdf417::render_ccb(&bytes, 4)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::C => {
return Err(crate::error::Error::InvalidData(
"composite_databar_omni_ccb: CC-C is only valid with GS1-128, \
not DataBar Omni"
.into(),
));
}
};
Ok(build_databaromni_composite(
&cc_bm,
&linsbs,
DATABAROMNI_LINHEIGHT,
))
}
fn apply_sepfinder(bot: &[u8], sep: &mut [u8], fp: usize) {
for i in fp..=fp + 12 {
if i >= bot.len() {
break;
}
let v = if bot[i] == 0 {
let prev_bot = if i > 0 { bot[i - 1] } else { 0 };
if prev_bot == 1 {
1
} else {
let prev_sep = if i > 0 { sep[i - 1] } else { 0 };
u8::from(prev_sep == 0)
}
} else {
0
};
sep[i] = v;
}
let matches = (0..=12).all(|j| {
let pos = fp + j;
pos < bot.len() && bot[pos] == DATABAROMNI_F3PAT[j]
});
if matches {
for (j, &v) in DATABAROMNI_FINDERSEP.iter().enumerate() {
if fp + j < sep.len() {
sep[fp + j] = v;
}
}
}
}
pub(crate) const GS1_128_LINHEIGHT: usize = 36;
pub(crate) const EAN_LINHEIGHT: usize = 72;
pub(crate) const EAN13_LINWIDTH: usize = 95;
#[allow(dead_code)]
pub(crate) const EAN8_LINWIDTH: usize = 67;
fn ean_guard_rows(pixx: usize, linpad_len: usize, linwidth: usize) -> [Vec<u8>; 3] {
let mut row_a = vec![0u8; pixx];
let mut row_b = vec![0u8; pixx];
let set = |row: &mut [u8], idx: usize| {
if idx < row.len() {
row[idx] = 1;
}
};
set(&mut row_a, linpad_len + 1);
set(&mut row_a, linpad_len + linwidth);
set(&mut row_b, linpad_len);
set(&mut row_b, linpad_len + linwidth + 1);
let row_c = row_a.clone();
[row_a, row_b, row_c]
}
pub(crate) fn build_ean_cca_composite(
cc_pixs: &crate::encoding::BitMatrix,
linsbs: &[u32],
linwidth: usize,
linheight: usize,
) -> crate::encoding::BitMatrix {
let ccpixx = cc_pixs.width();
let linpad_len = ccpixx.saturating_sub(linwidth + 2);
let diff_signed: isize = linwidth as isize + linpad_len as isize + 1 - ccpixx as isize;
let ccrpad_len = diff_signed.max(0) as usize;
let lin_trailing_zero = if diff_signed < 0 { 1usize } else { 0 };
let pixx = ccpixx + ccrpad_len;
let cc_rows = cc_pixs.height();
let guard_rowmult = 2;
let pixy = cc_rows * CCA_ROWMULT + 3 * guard_rowmult + linheight;
let mut bm = crate::encoding::BitMatrix::new(pixx, pixy);
for r in 0..cc_rows {
for rep in 0..CCA_ROWMULT {
let y = r * CCA_ROWMULT + rep;
for x in 0..ccpixx {
bm.set(x, y, cc_pixs.get(x, r));
}
}
}
let guard_rows = ean_guard_rows(pixx, linpad_len, linwidth);
for (g, guard) in guard_rows.iter().enumerate() {
for rep in 0..guard_rowmult {
let y = cc_rows * CCA_ROWMULT + g * guard_rowmult + rep;
for (x, &v) in guard.iter().enumerate() {
bm.set(x, y, v == 1);
}
}
}
let linpixs = sbs_to_pixels(linsbs);
let mut linear_row = vec![0u8; pixx];
let lin_start = linpad_len + 1;
for (i, &v) in linpixs.iter().enumerate() {
linear_row[lin_start + i] = v;
}
let _ = lin_trailing_zero;
for rep in 0..linheight {
let y = cc_rows * CCA_ROWMULT + 3 * guard_rowmult + rep;
for (x, &v) in linear_row.iter().enumerate() {
bm.set(x, y, v == 1);
}
}
bm
}
fn build_ean_family_composite(
cc: &crate::symbology::gs1_cc::CcEncoded,
cccolumns: u8,
linsbs: &[u32],
linwidth: usize,
linheight: usize,
handler_name: &'static str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let cc_bm = match cc.version {
crate::symbology::gs1_cc::CcVersion::A => {
let (bm, _) = crate::symbology::micropdf417::render_cca(&cc.codewords, cccolumns)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::B => {
let bytes: Vec<u8> = cc.codewords.iter().map(|&v| v as u8).collect();
let (bm, _) = crate::symbology::micropdf417::render_ccb(&bytes, cccolumns)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::C => {
return Err(crate::error::Error::InvalidData(format!(
"{handler_name}: CC-C is only valid with GS1-128, not EAN/UPC",
)));
}
};
Ok(build_ean_cca_composite(&cc_bm, linsbs, linwidth, linheight))
}
pub(crate) fn encode_ean13_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let pat = crate::symbology::ean::encode_ean13(linear, &crate::options::Options::default())?;
let linsbs: Vec<u32> = pat.bars.iter().map(|&b| u32::from(b)).collect();
let linwidth: usize = linsbs.iter().map(|&w| w as usize).sum();
debug_assert_eq!(linwidth, EAN13_LINWIDTH);
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_ean13_cca: payload requires CC-B; \
use composite_ean13_ccb instead"
.into(),
));
}
let (cc_bm, _metric) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_ean_cca_composite(
&cc_bm,
&linsbs,
linwidth,
EAN_LINHEIGHT,
))
}
pub(crate) fn encode_ean13_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let pat = crate::symbology::ean::encode_ean13(linear, &crate::options::Options::default())?;
let linsbs: Vec<u32> = pat.bars.iter().map(|&b| u32::from(b)).collect();
let linwidth: usize = linsbs.iter().map(|&w| w as usize).sum();
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
build_ean_family_composite(
&cc,
4,
&linsbs,
linwidth,
EAN_LINHEIGHT,
"composite_ean13_ccb",
)
}
pub(crate) fn encode_upca_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let pat = crate::symbology::ean::encode_upca(linear, &crate::options::Options::default())?;
let linsbs: Vec<u32> = pat.bars.iter().map(|&b| u32::from(b)).collect();
let linwidth: usize = linsbs.iter().map(|&w| w as usize).sum();
debug_assert_eq!(linwidth, EAN13_LINWIDTH); let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_upca_cca: payload requires CC-B; use composite_upca_ccb instead".into(),
));
}
let (cc_bm, _) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_ean_cca_composite(
&cc_bm,
&linsbs,
linwidth,
EAN_LINHEIGHT,
))
}
pub(crate) fn encode_upca_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let pat = crate::symbology::ean::encode_upca(linear, &crate::options::Options::default())?;
let linsbs: Vec<u32> = pat.bars.iter().map(|&b| u32::from(b)).collect();
let linwidth: usize = linsbs.iter().map(|&w| w as usize).sum();
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
build_ean_family_composite(
&cc,
4,
&linsbs,
linwidth,
EAN_LINHEIGHT,
"composite_upca_ccb",
)
}
pub(crate) fn encode_ean8_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let pat = crate::symbology::ean::encode_ean8(linear, &crate::options::Options::default())?;
let linsbs: Vec<u32> = pat.bars.iter().map(|&b| u32::from(b)).collect();
let linwidth: usize = linsbs.iter().map(|&w| w as usize).sum();
debug_assert_eq!(linwidth, EAN8_LINWIDTH);
let cc = crate::symbology::gs1_cc::encode_cc(comp, 3)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_ean8_cca: payload requires CC-B; use composite_ean8_ccb instead".into(),
));
}
let (cc_bm, _) = crate::symbology::micropdf417::render_cca(&cc.codewords, 3)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_ean_cca_composite(
&cc_bm,
&linsbs,
linwidth,
EAN_LINHEIGHT,
))
}
pub(crate) fn encode_ean8_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let pat = crate::symbology::ean::encode_ean8(linear, &crate::options::Options::default())?;
let linsbs: Vec<u32> = pat.bars.iter().map(|&b| u32::from(b)).collect();
let linwidth: usize = linsbs.iter().map(|&w| w as usize).sum();
let cc = crate::symbology::gs1_cc::encode_cc(comp, 3)?;
build_ean_family_composite(
&cc,
3,
&linsbs,
linwidth,
EAN_LINHEIGHT,
"composite_ean8_ccb",
)
}
pub(crate) const UPCE_LINWIDTH: usize = 51;
pub(crate) const DATABAREXPANDED_SEPLEFT: usize = 3;
pub(crate) const DATABAREXPANDED_SEPRIGHT: usize = 4;
pub(crate) const DATABAREXPANDED_LINHEIGHT: usize = 34;
fn databarexpanded_bot(linsbs: &[u8]) -> Vec<u8> {
let total: usize = linsbs.iter().map(|&w| w as usize).sum();
let mut bot = Vec::with_capacity(total);
let mut bit = 1u8;
for &w in linsbs {
for _ in 0..w {
bot.push(bit);
}
bit ^= 1;
}
bot
}
fn apply_databarexpanded_sepfinder(bot: &[u8], sep: &mut [u8], fp: usize) {
for i in fp..=fp + 12 {
if i >= bot.len() {
break;
}
let v = if bot[i] == 0 {
let prev_bot = if i > 0 { bot[i - 1] } else { 0 };
if prev_bot == 1 {
1
} else {
let prev_sep = if i > 0 { sep[i - 1] } else { 0 };
u8::from(prev_sep == 0)
}
} else {
0
};
sep[i] = v;
}
}
pub(crate) fn databarexpanded_separator(linsbs: &[u8]) -> Vec<u8> {
let bot = databarexpanded_bot(linsbs);
let mut sep: Vec<u8> = bot.iter().map(|&b| 1 - b).collect();
for s in sep.iter_mut().take(DATABAREXPANDED_SEPLEFT) {
*s = 0;
}
let n = sep.len();
for s in sep.iter_mut().skip(n - DATABAREXPANDED_SEPRIGHT) {
*s = 0;
}
let len_cap = bot.len().saturating_sub(13);
let mut fp = 18usize;
while fp <= len_cap {
apply_databarexpanded_sepfinder(&bot, &mut sep, fp);
fp += 98;
}
let mut fp = 69usize;
while fp <= len_cap {
apply_databarexpanded_sepfinder(&bot, &mut sep, fp);
fp += 98;
}
sep
}
pub(crate) fn build_databar_expanded_composite(
cc_pixs: &crate::encoding::BitMatrix,
linsbs: &[u8],
linheight: usize,
) -> crate::encoding::BitMatrix {
let linsbs_sum: usize = linsbs.iter().map(|&w| w as usize).sum();
let pixx = linsbs_sum + 1;
let cc_width = cc_pixs.width();
let diff = pixx as isize - cc_width as isize;
let cc_rows = cc_pixs.height();
let pixy = cc_rows * CCA_ROWMULT + 1 + linheight;
let mut bm = crate::encoding::BitMatrix::new(pixx, pixy);
for r in 0..cc_rows {
for rep in 0..CCA_ROWMULT {
let y = r * CCA_ROWMULT + rep;
for x in 0..cc_width {
bm.set(2 + x, y, cc_pixs.get(x, r));
}
}
}
let sep = databarexpanded_separator(linsbs);
let sep_y = cc_rows * CCA_ROWMULT;
for (x, &v) in sep.iter().enumerate() {
bm.set(1 + x, sep_y, v == 1);
}
let bot = databarexpanded_bot(linsbs);
for rep in 0..linheight {
let y = cc_rows * CCA_ROWMULT + 1 + rep;
for (x, &v) in bot.iter().enumerate() {
bm.set(1 + x, y, v == 1);
}
}
let _ = diff; bm
}
pub(crate) fn encode_databar_expanded_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let pat = crate::symbology::databar_expanded::encode(linear, true)?;
let linsbs: Vec<u8> = pat.bars.clone();
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_databar_expanded_cca: payload requires CC-B; \
use composite_databar_expanded_ccb instead"
.into(),
));
}
let (cc_bm, _) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_databar_expanded_composite(
&cc_bm,
&linsbs,
DATABAREXPANDED_LINHEIGHT,
))
}
pub(crate) fn encode_databar_expanded_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let pat = crate::symbology::databar_expanded::encode(linear, true)?;
let linsbs: Vec<u8> = pat.bars.clone();
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
let cc_bm = match cc.version {
crate::symbology::gs1_cc::CcVersion::A => {
let (bm, _) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::B => {
let bytes: Vec<u8> = cc.codewords.iter().map(|&v| v as u8).collect();
let (bm, _) = crate::symbology::micropdf417::render_ccb(&bytes, 4)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::C => {
return Err(crate::error::Error::InvalidData(
"composite_databar_expanded_ccb: CC-C is only valid with GS1-128".into(),
));
}
};
Ok(build_databar_expanded_composite(
&cc_bm,
&linsbs,
DATABAREXPANDED_LINHEIGHT,
))
}
pub(crate) fn encode_upce_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let pat = crate::symbology::ean::encode_upce(linear, &crate::options::Options::default())?;
let linsbs: Vec<u32> = pat.bars.iter().map(|&b| u32::from(b)).collect();
let linwidth: usize = linsbs.iter().map(|&w| w as usize).sum();
debug_assert_eq!(linwidth, UPCE_LINWIDTH);
let cc = crate::symbology::gs1_cc::encode_cc(comp, 2)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_upce_cca: payload requires CC-B; use composite_upce_ccb instead".into(),
));
}
let (cc_bm, _) = crate::symbology::micropdf417::render_cca(&cc.codewords, 2)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_ean_cca_composite(
&cc_bm,
&linsbs,
linwidth,
EAN_LINHEIGHT,
))
}
pub(crate) fn encode_upce_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let pat = crate::symbology::ean::encode_upce(linear, &crate::options::Options::default())?;
let linsbs: Vec<u32> = pat.bars.iter().map(|&b| u32::from(b)).collect();
let linwidth: usize = linsbs.iter().map(|&w| w as usize).sum();
let cc = crate::symbology::gs1_cc::encode_cc(comp, 2)?;
build_ean_family_composite(
&cc,
2,
&linsbs,
linwidth,
EAN_LINHEIGHT,
"composite_upce_ccb",
)
}
pub(crate) fn gs1_128_separator(linsbs: &[u32]) -> Vec<u8> {
let total: usize = linsbs.iter().map(|&w| w as usize).sum();
let mut sep = Vec::with_capacity(total);
let mut bit = 0u8;
for &w in linsbs {
for _ in 0..w {
sep.push(bit);
}
bit ^= 1;
}
sep
}
pub(crate) fn gs1_128_cc_offset_a(linwidth: usize) -> isize {
let s = ((linwidth as isize) - 2) / 11;
let p = (s - 9) / 2;
let base = (s - p - 1) * 11 + 10 + if p == 0 { 2 } else { 0 };
base - 99
}
pub(crate) fn build_gs1_128_cca_composite(
cc_pixs: &crate::encoding::BitMatrix,
linsbs: &[u32],
linheight: usize,
) -> crate::encoding::BitMatrix {
let linwidth: usize = linsbs.iter().map(|&w| w as usize).sum();
let x = gs1_128_cc_offset_a(linwidth);
let cc_width = cc_pixs.width();
debug_assert!(x >= 0, "GS1-128 CC-A/B offset should be non-negative");
let cclpad = x as usize;
let diff = linwidth as isize - (cc_width as isize + x);
let ccrpad = diff.max(0) as usize;
let pixx = (cclpad + cc_width + ccrpad).max(linwidth);
let cc_rows = cc_pixs.height();
let pixy = cc_rows * CCA_ROWMULT + 1 + linheight;
let mut bm = crate::encoding::BitMatrix::new(pixx, pixy);
for r in 0..cc_rows {
for rep in 0..CCA_ROWMULT {
let y = r * CCA_ROWMULT + rep;
for x_in in 0..cc_width {
bm.set(cclpad + x_in, y, cc_pixs.get(x_in, r));
}
}
}
let sep = gs1_128_separator(linsbs);
let sep_y = cc_rows * CCA_ROWMULT;
for (x_in, &v) in sep.iter().enumerate() {
bm.set(x_in, sep_y, v == 1);
}
let linpixs = sbs_to_pixels(linsbs);
for rep in 0..linheight {
let y = cc_rows * CCA_ROWMULT + 1 + rep;
for (x_in, &v) in linpixs.iter().enumerate() {
bm.set(x_in, y, v == 1);
}
}
bm
}
fn gs1_128_linkage_sbs(
data: &str,
linkage: crate::symbology::gs1_128::Linkage,
) -> Result<Vec<u32>, crate::error::Error> {
let pat = crate::symbology::gs1_128::encode_with_linkage(
data,
&crate::options::Options::default(),
linkage,
)?;
Ok(pat.bars.iter().map(|&b| u32::from(b)).collect())
}
pub(crate) fn encode_gs1_128_cca(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let linsbs = gs1_128_linkage_sbs(linear, crate::symbology::gs1_128::Linkage::A)?;
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
if cc.version != crate::symbology::gs1_cc::CcVersion::A {
return Err(crate::error::Error::InvalidData(
"composite_gs1_128_cca: payload requires CC-B; \
use composite_gs1_128_ccb instead"
.into(),
));
}
let (cc_bm, _metric) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_gs1_128_cca_composite(
&cc_bm,
&linsbs,
GS1_128_LINHEIGHT,
))
}
pub(crate) const PDF417_ROWMULT: usize = 3;
pub(crate) const GS1_128_CC_OFFSET_C: isize = -7;
pub(crate) fn build_gs1_128_ccc_composite(
cc_pixs: &crate::encoding::BitMatrix,
linsbs: &[u32],
linheight: usize,
) -> crate::encoding::BitMatrix {
let linwidth: usize = linsbs.iter().map(|&w| w as usize).sum();
let x = GS1_128_CC_OFFSET_C; let cc_width = cc_pixs.width();
debug_assert!(x < 0, "CC-C uses x = -7 < 0");
let cclpad = 0usize;
let linlpad = (-x) as usize;
let diff = linwidth as isize - (cc_width as isize + x);
let (ccrpad, linrpad) = if diff > 0 {
(diff as usize, 0usize)
} else {
(0usize, (-diff) as usize)
};
let pixx = (cclpad + cc_width + ccrpad).max(linlpad + linwidth + linrpad);
let cc_rows = cc_pixs.height();
let pixy = cc_rows * PDF417_ROWMULT + 1 + linheight;
let mut bm = crate::encoding::BitMatrix::new(pixx, pixy);
for r in 0..cc_rows {
for rep in 0..PDF417_ROWMULT {
let y = r * PDF417_ROWMULT + rep;
for x_in in 0..cc_width {
bm.set(cclpad + x_in, y, cc_pixs.get(x_in, r));
}
}
}
let sep = gs1_128_separator(linsbs);
let sep_y = cc_rows * PDF417_ROWMULT;
for (x_in, &v) in sep.iter().enumerate() {
bm.set(linlpad + x_in, sep_y, v == 1);
}
let linpixs = sbs_to_pixels(linsbs);
for rep in 0..linheight {
let y = cc_rows * PDF417_ROWMULT + 1 + rep;
for (x_in, &v) in linpixs.iter().enumerate() {
bm.set(linlpad + x_in, y, v == 1);
}
}
bm
}
pub(crate) fn encode_gs1_128_ccc(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let linsbs = gs1_128_linkage_sbs(linear, crate::symbology::gs1_128::Linkage::C)?;
let linwidth: u32 = linsbs.iter().sum();
let (cc_bytes, size) = crate::symbology::gs1_cc::encode_cc_force_c(comp, linwidth)?;
let cc_bm =
crate::symbology::pdf417::pdf417_render_ccc(&cc_bytes, size.eclevel, size.columns as usize)
.map_err(crate::error::Error::InvalidData)?;
Ok(build_gs1_128_ccc_composite(
&cc_bm,
&linsbs,
GS1_128_LINHEIGHT,
))
}
pub(crate) fn encode_gs1_128_ccb(
input: &str,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let (linear, comp) = split_composite_input(input)?;
let linsbs = gs1_128_linkage_sbs(linear, crate::symbology::gs1_128::Linkage::A)?;
let cc = crate::symbology::gs1_cc::encode_cc(comp, 4)?;
let cc_bm = match cc.version {
crate::symbology::gs1_cc::CcVersion::A => {
let (bm, _m) = crate::symbology::micropdf417::render_cca(&cc.codewords, 4)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::B => {
let bytes: Vec<u8> = cc.codewords.iter().map(|&v| v as u8).collect();
let (bm, _m) = crate::symbology::micropdf417::render_ccb(&bytes, 4)
.map_err(crate::error::Error::InvalidData)?;
bm
}
crate::symbology::gs1_cc::CcVersion::C => {
return Err(crate::error::Error::InvalidData(
"composite_gs1_128_ccb: CC-C should use composite_gs1_128_ccc instead".into(),
));
}
};
Ok(build_gs1_128_cca_composite(
&cc_bm,
&linsbs,
GS1_128_LINHEIGHT,
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_composite_input_basic() {
let (l, c) = split_composite_input("(01)12345|(10)BATCH").unwrap();
assert_eq!(l, "(01)12345");
assert_eq!(c, "(10)BATCH");
}
#[test]
fn split_composite_input_rejects_no_pipe() {
let err = split_composite_input("(01)12345").unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("expected InvalidData; got {err:?}");
};
assert!(
msg.contains("composite:")
&& msg.contains("LINEAR|COMP")
&& msg.contains("both non-empty"),
"no-pipe diagnostic must pin tag + format hint + non-empty requirement; got {msg:?}"
);
}
#[test]
fn split_composite_input_rejects_empty_half() {
for input in ["|(10)BATCH", "(01)12345|"] {
let err = split_composite_input(input).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("{input:?} must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("composite:")
&& msg.contains("LINEAR|COMP")
&& msg.contains("both non-empty"),
"{input:?} empty-half must pin tag + format hint + non-empty requirement; \
got {msg:?}"
);
}
}
#[test]
fn sbs_to_pixels_alternates() {
let pixs = sbs_to_pixels(&[3, 2, 1]);
assert_eq!(pixs, vec![1, 1, 1, 0, 0, 1]);
}
#[test]
fn sbs_to_pixels_zero_width() {
let pixs = sbs_to_pixels(&[1, 0, 1]);
assert_eq!(pixs, vec![1, 1]);
}
#[test]
fn databarexpanded_bot_pins_alternation_and_zero_width_toggle() {
assert_eq!(
databarexpanded_bot(&[]),
Vec::<u8>::new(),
"empty input → empty"
);
assert_eq!(
databarexpanded_bot(&[5]),
vec![1, 1, 1, 1, 1],
"single bar [5] = 5 ones (pins bit=1 start)"
);
assert_eq!(
databarexpanded_bot(&[3, 2, 1]),
vec![1, 1, 1, 0, 0, 1],
"[3,2,1] = bar3 + space2 + bar1"
);
assert_eq!(
databarexpanded_bot(&[1, 0, 1]),
vec![1, 1],
"[1,0,1] = bar1 + (zero-width space, bit still toggles) + bar1"
);
assert_eq!(
databarexpanded_bot(&[2, 1, 3, 2]),
vec![1, 1, 0, 1, 1, 1, 0, 0],
"[2,1,3,2] = bar2 + space1 + bar3 + space2 (full 4-toggle cycle)"
);
}
#[test]
fn databaromni_separator_basic_shape() {
let bot = vec![1u8; 96];
let sep = databaromni_separator(&bot);
assert_eq!(sep.len(), 96);
assert!(sep.iter().all(|&v| v == 0));
}
#[test]
fn databaromni_separator_pins_both_fp_18_and_fp_64() {
let bot = vec![0u8; 96];
let sep = databaromni_separator(&bot);
assert_eq!(sep.len(), 96);
for i in 0..3 {
assert_eq!(sep[i], 0, "front pad pos {i}");
}
for i in 92..96 {
assert_eq!(sep[i], 0, "back pad pos {i}");
}
assert_eq!(
&sep[18..=30],
&[0u8, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
"fp=18 window must alternate starting with 0"
);
assert_eq!(
&sep[64..=76],
&[0u8, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
"fp=64 window must alternate starting with 0; \
a mutant on fp=64 → 63 or 65 would shift this"
);
for i in 31..64 {
assert_eq!(
sep[i], 1,
"pos {i}: between sepfinder windows, inverted to 1"
);
}
for i in 77..92 {
assert_eq!(sep[i], 1, "pos {i}: after fp=64 window, inverted to 1");
}
}
#[test]
fn databarexpandedstacked_composite_separator_boundary() {
let top = vec![1u8; 60];
let sep = databarexpandedstacked_composite_separator(&top);
assert_eq!(sep.len(), 60, "output preserves input length");
assert!(sep.iter().all(|&v| v == 0), "all-1s → all-0s (invert)");
let top = vec![0u8; 50];
let sep = databarexpandedstacked_composite_separator(&top);
assert_eq!(sep.len(), 50);
for i in 0..4 {
assert_eq!(sep[i], 0, "front pad pos {i}");
}
for i in 46..50 {
assert_eq!(sep[i], 0, "back pad pos {i}");
}
assert_eq!(sep[10], 1, "position 10 inverted, untouched");
assert_eq!(sep[45], 1, "position 45 inverted, untouched");
let top = vec![0u8; 31];
let sep = databarexpandedstacked_composite_separator(&top);
assert_eq!(sep.len(), 31);
for i in 0..4 {
assert_eq!(sep[i], 0);
}
for i in 27..31 {
assert_eq!(sep[i], 0);
}
for i in 4..27 {
assert_eq!(sep[i], 1, "pos {i}: no sepfinder fired");
}
let top = vec![0u8; 85];
let sep = databarexpandedstacked_composite_separator(&top);
assert_eq!(sep.len(), 85);
assert_eq!(
sep[70], 0,
"loop-2 sepfinder at fp=70 must start with sep[70]=0 \
(catches mutant on loop-2 starting position)"
);
assert_eq!(sep[71], 1, "sepfinder alternation: sep[71]=1");
assert_eq!(sep[82], 0, "sepfinder window end: sep[82]=0");
assert_eq!(
sep[81], 1,
"sepfinder must overwrite back-pad zero with alternation value (1); \
order mutation (sepfinder before back-pad) would leave it 0"
);
assert_eq!(sep[19], 0, "loop-1 sepfinder at fp=19 still fires for n=85");
for i in 32..70 {
assert_eq!(
sep[i], 1,
"pos {i}: between sepfinder windows, inverted to 1"
);
}
}
#[test]
fn databarstacked_composite_separator_boundary_and_invert() {
let top = [1u8; 50];
let sep = databarstacked_composite_separator(&top);
assert_eq!(sep.len(), 50, "output is exactly 50 cells");
assert!(
sep.iter().all(|&v| v == 0),
"all-1s top → inverted to all-0s; sepfinder zero-padded too"
);
let top = [0u8; 50];
let sep = databarstacked_composite_separator(&top);
assert_eq!(sep.len(), 50);
for i in 0..4 {
assert_eq!(sep[i], 0, "front pad pos {i} zeroed");
}
for i in 46..50 {
assert_eq!(sep[i], 0, "back pad pos {i} zeroed");
}
assert_eq!(
sep[10], 1,
"position 10 (non-pad, non-sepfinder) inverts to 1 and stays"
);
assert_eq!(sep[45], 1, "position 45 (non-pad) stays inverted to 1");
}
#[test]
fn cca_post_ecc_cws_for_batch_match_bwip_js() {
let enc = crate::symbology::gs1_cc::encode_cc("(10)BATCH", 4).unwrap();
let cws_in: Vec<u32> = enc.codewords.clone();
assert_eq!(cws_in, vec![637, 279, 478, 709, 810, 262, 840, 132]);
let mut cws: Vec<u16> = cws_in.iter().map(|&v| v as u16).collect();
cws.resize(8, 900);
let check = crate::util::rs_gf929::encode(&cws[..8], 4);
cws.extend_from_slice(&check);
assert_eq!(
cws,
vec![637, 279, 478, 709, 810, 262, 840, 132, 284, 907, 528, 214],
"post-ECC cws mismatch — diagnose RS encoding",
);
}
#[test]
fn cca_render_for_batch_matches_bwip_js_row2() {
let enc = crate::symbology::gs1_cc::encode_cc("(10)BATCH", 4).unwrap();
let (bm, _) = crate::symbology::micropdf417::render_cca(&enc.codewords, 4).unwrap();
let want_row2_99: [u8; 99] = [
1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1,
1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1,
];
for (x, &want) in want_row2_99.iter().enumerate() {
let got = u8::from(bm.get(x, 2));
assert_eq!(got, want, "row 2 col {x} mismatch");
}
}
#[test]
fn cca_render_for_batch_matches_bwip_js_first_row() {
let enc = crate::symbology::gs1_cc::encode_cc("(10)BATCH", 4).unwrap();
let (bm, _) = crate::symbology::micropdf417::render_cca(&enc.codewords, 4).unwrap();
assert_eq!(bm.width(), 99);
assert_eq!(bm.height(), 3);
let want_row0_99: [u8; 99] = [
1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0,
1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0,
0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1,
];
for (x, &want) in want_row0_99.iter().enumerate() {
let got = u8::from(bm.get(x, 0));
assert_eq!(got, want, "row 0 col {x} mismatch");
}
}
#[test]
fn encode_databaromni_cca_matches_bwip_js_pixs_first_8_rows() {
let bm = encode_databaromni_cca("(01)24012345678905|(10)BATCH").unwrap();
assert_eq!(bm.width(), 100);
assert_eq!(bm.height(), 40);
let want_rows: [[u8; 100]; 8] = [
[
1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0,
0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0,
],
[
1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0,
0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0,
],
[
1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1,
1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0,
1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0,
],
[
1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1,
1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0,
1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0,
],
[
1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1,
0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1,
0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1,
0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
],
[
1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1,
0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1,
0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1,
0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
],
[
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0,
1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1,
0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
],
[
0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1,
0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
],
];
for (y, want_row) in want_rows.iter().enumerate() {
for (x, &want) in want_row.iter().enumerate() {
let got = u8::from(bm.get(x, y));
assert_eq!(got, want, "row {y} col {x} mismatch");
}
}
for y in 8..40 {
for x in 0..100 {
assert_eq!(
bm.get(x, y),
bm.get(x, 7),
"row {y} col {x} should be a copy of row 7 (linear template tile)"
);
}
}
}
#[test]
fn encode_databaromni_cca_smoke() {
let bm = encode_databaromni_cca("(01)24012345678905|(10)BATCH").unwrap();
assert_eq!(bm.width(), 100);
assert_eq!(bm.height(), 40);
}
#[test]
fn build_databaromni_composite_dimensions() {
let cc = crate::encoding::BitMatrix::new(99, 3);
let mut linsbs: Vec<u32> = (0..47).map(|_| 2u32).collect();
linsbs.push(1); assert_eq!(linsbs.iter().sum::<u32>(), 95);
let bm = build_databaromni_composite(&cc, &linsbs, 33);
assert_eq!(bm.width(), 100);
assert_eq!(bm.height(), 40);
}
#[test]
fn composite_databar_omni_cca_via_public_api() {
use crate::{Options, Symbology};
let opts = Options::default();
let enc = Symbology::CompositeDatabarOmniCca
.encode("(01)24012345678905|(10)BATCH", &opts)
.unwrap();
match enc {
crate::encoding::Encoded::Matrix(bm) => {
assert_eq!(
bm.width(),
100,
"CompositeDatabarOmniCca via Symbology must yield 100-col matrix; got {}",
bm.width()
);
assert_eq!(
bm.height(),
40,
"CompositeDatabarOmniCca via Symbology must yield 40-row matrix (linheight=39 + 1 separator); got {}",
bm.height()
);
}
other => panic!(
"Symbology::CompositeDatabarOmniCca must dispatch to Encoded::Matrix; got {other:?}"
),
}
}
#[test]
fn encode_databaromni_ccb_dimensions_match_bwip_js() {
let bm = encode_databaromni_ccb(
"(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap();
assert_eq!(bm.width(), 100);
assert_eq!(bm.height(), 58);
}
#[test]
fn encode_databaromni_ccb_matches_bwip_js_pixs_key_rows() {
let bm = encode_databaromni_ccb(
"(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap();
assert_eq!(bm.width(), 100);
assert_eq!(bm.height(), 58);
let want_cc_r0: [u8; 100] = [
1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0,
];
for (x, &want) in want_cc_r0.iter().enumerate() {
assert_eq!(
u8::from(bm.get(x, 0)),
want,
"y=0 col {x} mismatch (CC-B logical row 0)",
);
assert_eq!(
u8::from(bm.get(x, 1)),
want,
"y=1 col {x} should equal y=0 (rowmult=2 repeat)",
);
}
let want_cc_r11: [u8; 100] = [
1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1,
1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1,
0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0,
];
for (x, &want) in want_cc_r11.iter().enumerate() {
assert_eq!(
u8::from(bm.get(x, 22)),
want,
"y=22 col {x} mismatch (CC-B logical row 11)",
);
assert_eq!(
u8::from(bm.get(x, 23)),
want,
"y=23 col {x} should equal y=22",
);
}
let want_sep: [u8; 100] = [
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
];
for (x, &want) in want_sep.iter().enumerate() {
assert_eq!(u8::from(bm.get(x, 24)), want, "y=24 col {x} (sep)");
}
let want_lin: [u8; 100] = [
0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1,
1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0,
1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
];
for (x, &want) in want_lin.iter().enumerate() {
assert_eq!(u8::from(bm.get(x, 25)), want, "y=25 col {x} (lin)");
}
for y in 26..58 {
for x in 0..100 {
assert_eq!(bm.get(x, y), bm.get(x, 25), "y={y} col {x}: linear-tile");
}
}
}
#[test]
fn encode_databaromni_ccb_accepts_cca_size_payload() {
let bm = encode_databaromni_ccb("(01)24012345678905|(10)BATCH").unwrap();
assert_eq!(bm.width(), 100);
assert_eq!(bm.height(), 40);
}
#[test]
fn composite_databar_omni_cca_rejects_ccb_payload() {
let res = encode_databaromni_cca(
"(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
);
let err = res.expect_err("CC-B payload should be rejected");
let msg = format!("{err}");
assert!(
msg.contains("composite_databar_omni_cca:"),
"missing exact `composite_databar_omni_cca:` prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing `payload requires CC-B` predicate: {msg}"
);
assert!(
msg.contains("composite_databar_omni_ccb instead"),
"missing `composite_databar_omni_ccb instead` migration hint: {msg}"
);
}
#[test]
fn composite_databar_omni_cca_id_round_trip() {
use crate::Symbology;
let sym = Symbology::CompositeDatabarOmniCca;
assert_eq!(sym.id(), "composite_databar_omni_cca");
assert_eq!(Symbology::from_id(sym.id()), Some(sym));
}
#[test]
fn databarlimited_separator_matches_bwip_js_oracle() {
let linsbs: [u8; 46] = [
1, 1, 1, 3, 1, 1, 1, 2, 4, 1, 4, 1, 1, 2, 3, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1,
2, 1, 2, 1, 1, 2, 3, 2, 1, 3, 2, 2, 2, 2, 1, 1, 5,
];
let want_sep: [u8; 74] = [
0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1,
0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1,
0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0,
];
let sep = databarlimited_separator(&linsbs);
assert_eq!(sep, want_sep);
}
#[test]
fn databarlimited_linpixs_matches_bwip_js_oracle() {
let linsbs: [u8; 46] = [
1, 1, 1, 3, 1, 1, 1, 2, 4, 1, 4, 1, 1, 2, 3, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1,
2, 1, 2, 1, 1, 2, 3, 2, 1, 3, 2, 2, 2, 2, 1, 1, 5,
];
let want_lin: [u8; 74] = [
0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0,
1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0,
1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1,
];
let lin = databarlimited_linpixs(&linsbs);
assert_eq!(lin, want_lin);
}
#[test]
fn encode_databarlimited_cca_dimensions_match_bwip_js() {
let bm = encode_databarlimited_cca("(01)15012345678907|(99)1234567").unwrap();
assert_eq!(bm.width(), 74);
assert_eq!(bm.height(), 19);
}
#[test]
fn encode_databarlimited_cca_rejects_ccb_payload() {
let res = encode_databarlimited_cca(
"(01)15012345678907|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK",
);
let err = res.expect_err("CC-B-sized payload must be rejected by CC-A path");
let msg = format!("{err}");
assert!(
msg.contains("composite_databar_limited_cca:"),
"missing exact `composite_databar_limited_cca:` prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing `payload requires CC-B` predicate: {msg}"
);
assert!(
msg.contains("composite_databar_limited_ccb instead"),
"missing `composite_databar_limited_ccb instead` migration hint: {msg}"
);
}
#[test]
fn encode_databarlimited_cca_matches_bwip_js_cc_rows() {
let bm = encode_databarlimited_cca("(01)15012345678907|(99)1234567").unwrap();
let want_cc_rows: [[u8; 74]; 4] = [
[
0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0,
1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0,
],
[
0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0,
1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1,
0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0,
],
[
0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0,
1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0,
],
[
0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0,
1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0,
0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0,
],
];
for (r, want) in want_cc_rows.iter().enumerate() {
let y_a = r * CCA_ROWMULT;
let y_b = r * CCA_ROWMULT + 1;
for (x, &w) in want.iter().enumerate() {
assert_eq!(u8::from(bm.get(x, y_a)), w, "y={y_a} col {x}");
assert_eq!(u8::from(bm.get(x, y_b)), w, "y={y_b} col {x}");
}
}
}
#[test]
fn encode_databarlimited_cca_matches_bwip_js_separator_and_linear() {
let bm = encode_databarlimited_cca("(01)15012345678907|(99)1234567").unwrap();
let want_sep: [u8; 74] = [
0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1,
0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1,
0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0,
];
let want_lin: [u8; 74] = [
0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0,
1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0,
1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1,
];
for x in 0..74 {
assert_eq!(u8::from(bm.get(x, 8)), want_sep[x], "sep col {x}");
assert_eq!(u8::from(bm.get(x, 9)), want_lin[x], "lin col {x}");
}
for y in 10..19 {
for x in 0..74 {
assert_eq!(bm.get(x, y), bm.get(x, 9), "y={y} col {x}");
}
}
}
#[test]
fn encode_databarlimited_ccb_dimensions_match_bwip_js() {
let bm = encode_databarlimited_ccb(
"(01)15012345678907|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap();
assert_eq!(bm.width(), 83);
assert_eq!(bm.height(), 51);
}
#[test]
fn encode_databarlimited_ccb_accepts_cca_payload() {
let bm = encode_databarlimited_ccb("(01)15012345678907|(99)1234567").unwrap();
assert_eq!(bm.width(), 74);
assert_eq!(bm.height(), 19);
}
#[test]
fn encode_databarlimited_ccb_matches_bwip_js_separator_and_linear() {
let bm = encode_databarlimited_ccb(
"(01)15012345678907|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap();
let want_sep: [u8; 83] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0,
];
let want_lin: [u8; 83] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1,
1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1,
];
for x in 0..83 {
assert_eq!(u8::from(bm.get(x, 40)), want_sep[x], "sep col {x}");
assert_eq!(u8::from(bm.get(x, 41)), want_lin[x], "lin col {x}");
}
for y in 42..51 {
for x in 0..83 {
assert_eq!(bm.get(x, y), bm.get(x, 41), "y={y} col {x}");
}
}
}
#[test]
fn encode_databarlimited_ccb_matches_bwip_js_cc_first_and_last_rows() {
let bm = encode_databarlimited_ccb(
"(01)15012345678907|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap();
let want_cc_row0: [u8; 83] = [
1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0,
1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0,
];
let want_cc_row19: [u8; 83] = [
1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0,
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0,
];
for x in 0..83 {
assert_eq!(u8::from(bm.get(x, 0)), want_cc_row0[x], "cc r0 col {x}");
assert_eq!(
u8::from(bm.get(x, 1)),
want_cc_row0[x],
"cc r0 col {x} (y=1)"
);
assert_eq!(u8::from(bm.get(x, 38)), want_cc_row19[x], "cc r19 col {x}");
assert_eq!(
u8::from(bm.get(x, 39)),
want_cc_row19[x],
"cc r19 col {x} (y=39)"
);
}
}
#[test]
fn gs1_128_cc_offset_a_for_known_linwidth() {
assert_eq!(gs1_128_cc_offset_a(145), 21);
assert!(gs1_128_cc_offset_a(123) >= 0);
}
#[test]
fn gs1_128_cc_offset_a_per_p_branch_arithmetic() {
assert_eq!(
gs1_128_cc_offset_a(101),
1,
"linwidth=101: s=9, p=0, +2 offset → 1"
);
assert_eq!(
gs1_128_cc_offset_a(112),
12,
"linwidth=112: s=10, p=0, +2 offset → 12"
);
assert_eq!(
gs1_128_cc_offset_a(123),
10,
"linwidth=123: s=11, p=1, +0 offset → 10"
);
assert_eq!(
gs1_128_cc_offset_a(145),
21,
"linwidth=145: s=13, p=2, +0 offset → 21"
);
}
#[test]
fn gs1_128_separator_starts_with_zero() {
let sep = gs1_128_separator(&[3, 2, 1]);
assert_eq!(sep, vec![0, 0, 0, 1, 1, 0]);
}
#[test]
fn gs1_128_separator_invariants_pin() {
assert_eq!(gs1_128_separator(&[]), Vec::<u8>::new());
let sep = gs1_128_separator(&[5]);
assert_eq!(sep, vec![0u8; 5], "single chunk → initial bit (0)");
let sep = gs1_128_separator(&[4, 3]);
assert_eq!(
sep,
vec![0, 0, 0, 0, 1, 1, 1],
"two chunks: 0s then 1s (alternation must fire)"
);
for input in [&[1u32, 1][..], &[5][..], &[3, 2, 1][..], &[2, 2, 2, 2][..]] {
let total: usize = input.iter().map(|&w| w as usize).sum();
assert_eq!(
gs1_128_separator(input).len(),
total,
"length must equal sum of widths for {input:?}"
);
}
let sep = gs1_128_separator(&[7, 5]);
for v in &sep[..7] {
assert_eq!(*v, 0, "first 7 bytes must be 0");
}
for v in &sep[7..12] {
assert_eq!(*v, 1, "next 5 bytes must be 1");
}
let sep = gs1_128_separator(&[0, 3]);
assert_eq!(
sep,
vec![1, 1, 1],
"width-0 chunk emits nothing but still toggles → 3 ones"
);
}
#[test]
fn encode_gs1_128_cca_dimensions_match_bwip_js() {
let bm = encode_gs1_128_cca("(01)04012345123456|(99)1234567").unwrap();
assert_eq!(bm.width(), 145);
assert_eq!(bm.height(), 43);
}
#[test]
fn encode_gs1_128_cca_matches_bwip_js_separator_and_linear() {
let bm = encode_gs1_128_cca("(01)04012345123456|(99)1234567").unwrap();
let want_sep: [u8; 145] = [
0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1,
0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0,
1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1,
1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0,
];
let want_lin: [u8; 145] = [
1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0,
1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1,
0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0,
0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0,
1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1,
];
for x in 0..145 {
assert_eq!(u8::from(bm.get(x, 6)), want_sep[x], "sep col {x}");
assert_eq!(u8::from(bm.get(x, 7)), want_lin[x], "lin col {x}");
}
for y in 8..43 {
for x in 0..145 {
assert_eq!(bm.get(x, y), bm.get(x, 7), "y={y} col {x}");
}
}
}
#[test]
fn encode_gs1_128_cca_matches_bwip_js_cc_row_0() {
let bm = encode_gs1_128_cca("(01)04012345123456|(99)1234567").unwrap();
let want_cc_row_0: [u8; 145] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1,
1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
for (x, &want) in want_cc_row_0.iter().enumerate() {
assert_eq!(u8::from(bm.get(x, 0)), want, "y=0 col {x}");
assert_eq!(u8::from(bm.get(x, 1)), want, "y=1 col {x}");
}
}
#[test]
fn encode_gs1_128_cca_rejects_ccb_payload() {
let res =
encode_gs1_128_cca("(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC");
let err = res.expect_err("CC-B-sized payload must be rejected by CC-A path");
let msg = format!("{err}");
assert!(
msg.contains("composite_gs1_128_cca:"),
"missing exact `composite_gs1_128_cca:` prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing `payload requires CC-B` predicate: {msg}"
);
assert!(
msg.contains("composite_gs1_128_ccb instead"),
"missing `composite_gs1_128_ccb instead` migration hint: {msg}"
);
}
#[test]
fn encode_gs1_128_ccb_accepts_both_cca_and_ccb_payloads() {
let bm_cca = encode_gs1_128_ccb("(01)04012345123456|(99)1234567").unwrap();
assert_eq!(bm_cca.width(), 145);
assert!(
bm_cca.height() > 30,
"CC-A path height must include CC rows + sep + linear, got {}",
bm_cca.height()
);
let bm_ccb =
encode_gs1_128_ccb("(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC")
.expect("CCB encoder must accept the CCB-sized payload");
assert_eq!(
bm_ccb.width(),
145,
"CC-B linear width stays 145 modules regardless of payload"
);
assert!(
bm_ccb.height() > bm_cca.height(),
"CC-B-sized payload should produce a taller symbol \
than CC-A-sized ({} vs {})",
bm_ccb.height(),
bm_cca.height()
);
}
#[test]
fn encode_ean13_cca_dimensions_match_bwip_js() {
let bm = encode_ean13_cca("5901234123457|(99)1234567").unwrap();
assert_eq!(bm.width(), 99);
assert_eq!(bm.height(), 84);
}
#[test]
fn encode_ean13_cca_matches_bwip_js_cc_row_0() {
let bm = encode_ean13_cca("5901234123457|(99)1234567").unwrap();
let want_cc0: [u8; 99] = [
1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1,
0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1,
];
for (x, &want) in want_cc0.iter().enumerate() {
assert_eq!(u8::from(bm.get(x, 0)), want, "y=0 col {x}");
assert_eq!(u8::from(bm.get(x, 1)), want, "y=1 col {x}");
}
}
#[test]
fn encode_ean13_cca_matches_bwip_js_guard_rows_and_linear() {
let bm = encode_ean13_cca("5901234123457|(99)1234567").unwrap();
for &y in &[6, 7, 10, 11] {
for x in 0..99 {
let want = if x == 3 || x == 97 { 1 } else { 0 };
assert_eq!(u8::from(bm.get(x, y)), want, "guard A y={y} col {x}");
}
}
for &y in &[8, 9] {
for x in 0..99 {
let want = if x == 2 || x == 98 { 1 } else { 0 };
assert_eq!(u8::from(bm.get(x, y)), want, "guard B y={y} col {x}");
}
}
let want_lin: [u8; 99] = [
0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0,
1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1,
1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0,
];
for (x, &want) in want_lin.iter().enumerate() {
assert_eq!(u8::from(bm.get(x, 12)), want, "lin y=12 col {x}");
}
for y in 13..84 {
for x in 0..99 {
assert_eq!(bm.get(x, y), bm.get(x, 12), "y={y} col {x}");
}
}
}
#[test]
fn ean_guard_rows_pin_cell_positions_and_row_c_identity() {
let rows = ean_guard_rows(20, 2, 10);
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].len(), 20, "row A length = pixx");
assert_eq!(rows[1].len(), 20, "row B length = pixx");
assert_eq!(rows[2].len(), 20, "row C length = pixx");
let mut expected_a = vec![0u8; 20];
expected_a[3] = 1;
expected_a[12] = 1;
assert_eq!(
rows[0], expected_a,
"row A cells at linpad+1=3 and linpad+linwidth=12"
);
let mut expected_b = vec![0u8; 20];
expected_b[2] = 1;
expected_b[13] = 1;
assert_eq!(
rows[1], expected_b,
"row B cells at linpad=2 and linpad+linwidth+1=13"
);
assert_eq!(
rows[2], expected_a,
"row C cells match row A (clone identity)"
);
assert_eq!(rows[2], rows[0], "row C IS clone of row A");
assert_ne!(
rows[2], rows[1],
"row C must NOT match row B (catches row_b.clone() mutation)"
);
let rows2 = ean_guard_rows(30, 5, 15);
let mut expected_a2 = vec![0u8; 30];
expected_a2[5 + 1] = 1; expected_a2[5 + 15] = 1; assert_eq!(rows2[0], expected_a2, "second size row A");
let mut expected_b2 = vec![0u8; 30];
expected_b2[5] = 1;
expected_b2[5 + 15 + 1] = 1; assert_eq!(rows2[1], expected_b2, "second size row B");
}
#[test]
fn encode_ean13_cca_rejects_ccb_payload() {
let res =
encode_ean13_cca("5901234123457|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK");
let err = res.expect_err("CC-B-sized payload must be rejected by CC-A path");
let msg = format!("{err}");
assert!(
msg.contains("composite_ean13_cca:"),
"missing exact `composite_ean13_cca:` prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing `payload requires CC-B` predicate: {msg}"
);
assert!(
msg.contains("composite_ean13_ccb instead"),
"missing `composite_ean13_ccb instead` migration hint: {msg}"
);
}
#[test]
fn encode_ean13_ccb_accepts_cca_payload() {
let bm = encode_ean13_ccb("5901234123457|(99)1234567").unwrap();
assert_eq!(bm.width(), 99);
assert_eq!(bm.height(), 84);
}
#[test]
fn encode_ean13_ccb_accepts_ccb_sized_payload() {
let bm_cca = encode_ean13_ccb("5901234123457|(99)1234567").unwrap();
let bm_ccb =
encode_ean13_ccb("5901234123457|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK")
.expect("CCB encoder must accept CCB-sized payload");
assert_eq!(
bm_ccb.width(),
bm_cca.width(),
"EAN-13 linear region width = 99 modules regardless of CC-A vs CC-B"
);
assert!(
bm_ccb.height() > bm_cca.height(),
"CC-B produces more rows than CC-A ({} vs {})",
bm_ccb.height(),
bm_cca.height()
);
}
#[test]
fn encode_upca_cca_dimensions_match_bwip_js() {
let bm = encode_upca_cca("012345678905|(99)1234567").unwrap();
assert_eq!(bm.width(), 99);
assert_eq!(bm.height(), 84);
}
#[test]
fn encode_upca_cca_rejects_ccb_payload() {
let res =
encode_upca_cca("012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK");
let err = res.expect_err("CC-B-sized payload must be rejected by CC-A path");
let msg = format!("{err}");
assert!(
msg.contains("composite_upca_cca:"),
"missing exact `composite_upca_cca:` prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing `payload requires CC-B` predicate: {msg}"
);
assert!(
msg.contains("composite_upca_ccb instead"),
"missing `composite_upca_ccb instead` migration hint: {msg}"
);
}
#[test]
fn encode_upca_ccb_dimensions_match_bwip_js() {
let bm = encode_upca_ccb("012345678905|(99)1234567").unwrap();
assert_eq!(bm.width(), 99);
assert_eq!(bm.height(), 84);
}
#[test]
fn encode_upca_ccb_accepts_ccb_sized_payload() {
let bm_cca = encode_upca_ccb("012345678905|(99)1234567").unwrap();
let bm_ccb =
encode_upca_ccb("012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK")
.expect("CCB encoder must accept CCB-sized payload");
assert_eq!(
bm_ccb.width(),
bm_cca.width(),
"UPC-A linear width = 99 modules regardless of CC-A vs CC-B"
);
assert!(
bm_ccb.height() > bm_cca.height(),
"CC-B produces more rows than CC-A ({} vs {})",
bm_ccb.height(),
bm_cca.height()
);
}
#[test]
fn encode_ean8_cca_dimensions_match_bwip_js() {
let bm = encode_ean8_cca("12345670|(99)1234567").unwrap();
assert_eq!(bm.width(), 72);
assert_eq!(bm.height(), 86);
}
#[test]
fn encode_ean8_cca_rejects_ccb_payload() {
let res = encode_ean8_cca("12345670|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK");
let err = res.expect_err("CC-B-sized payload must be rejected by CC-A path");
let msg = format!("{err}");
assert!(
msg.contains("composite_ean8_cca:"),
"missing exact `composite_ean8_cca:` prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing `payload requires CC-B` predicate: {msg}"
);
assert!(
msg.contains("composite_ean8_ccb instead"),
"missing `composite_ean8_ccb instead` migration hint: {msg}"
);
}
#[test]
fn encode_ean8_ccb_accepts_cca_payload() {
let bm = encode_ean8_ccb("12345670|(99)1234567").unwrap();
assert_eq!(bm.width(), 72);
assert_eq!(bm.height(), 86);
}
#[test]
fn encode_ean8_ccb_accepts_ccb_sized_payload() {
let bm_cca = encode_ean8_ccb("12345670|(99)1234567").unwrap();
let bm_ccb =
encode_ean8_ccb("12345670|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK")
.expect("CCB encoder must accept CCB-sized payload");
assert!(
bm_ccb.width() >= bm_cca.width(),
"CC-B width must equal or exceed CC-A width (CC-B may need wider CC)",
);
assert!(
bm_ccb.height() > bm_cca.height(),
"CC-B produces more rows than CC-A ({} vs {})",
bm_ccb.height(),
bm_cca.height()
);
assert!(
bm_ccb.width() < 200 && bm_ccb.height() > 50,
"expected EAN-8 composite shape (width < 200, height > 50), got {}×{}",
bm_ccb.width(),
bm_ccb.height(),
);
}
#[test]
fn encode_upce_cca_dimensions_match_bwip_js() {
let bm = encode_upce_cca("0123456|(99)1234567").unwrap();
assert_eq!(bm.width(), 55);
assert_eq!(bm.height(), 88);
}
#[test]
fn encode_upce_cca_rejects_ccb_payload() {
let res = encode_upce_cca("0123456|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK");
let err = res.expect_err("CC-B-sized payload must be rejected by CC-A path");
let msg = format!("{err}");
assert!(
msg.contains("composite_upce_cca:"),
"missing exact `composite_upce_cca:` prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing `payload requires CC-B` predicate: {msg}"
);
assert!(
msg.contains("composite_upce_ccb instead"),
"missing `composite_upce_ccb instead` migration hint: {msg}"
);
}
#[test]
fn encode_upce_ccb_accepts_cca_payload() {
let bm = encode_upce_ccb("0123456|(99)1234567").unwrap();
assert_eq!(bm.width(), 55);
assert_eq!(bm.height(), 88);
}
#[test]
fn encode_upce_ccb_accepts_ccb_sized_payload() {
let bm_cca = encode_upce_ccb("0123456|(99)1234567").unwrap();
let bm_ccb =
encode_upce_ccb("0123456|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK")
.expect("CCB encoder must accept CCB-sized payload");
assert!(
bm_ccb.width() >= bm_cca.width(),
"CC-B width must equal or exceed CC-A width (CC-B may need wider CC)",
);
assert!(
bm_ccb.height() > bm_cca.height(),
"CC-B produces more rows than CC-A ({} vs {})",
bm_ccb.height(),
bm_cca.height()
);
assert!(
bm_ccb.width() < 200 && bm_ccb.height() > 50,
"expected UPC-E composite shape, got {}×{}",
bm_ccb.width(),
bm_ccb.height(),
);
}
#[test]
fn encode_databar_expanded_cca_dimensions_match_bwip_js() {
let bm = encode_databar_expanded_cca("(01)90012345678908(3103)001750|(99)1234567").unwrap();
assert_eq!(bm.width(), 151);
assert_eq!(bm.height(), 41);
}
#[test]
fn encode_databar_expanded_cca_rejects_ccb_payload() {
let res = encode_databar_expanded_cca(
"(01)90012345678908(3103)001750|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK",
);
let err = res.expect_err("CC-B-sized payload must be rejected by CC-A path");
let msg = format!("{err}");
assert!(
msg.contains("composite_databar_expanded_cca:"),
"missing exact `composite_databar_expanded_cca:` prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing `payload requires CC-B` predicate: {msg}"
);
assert!(
msg.contains("composite_databar_expanded_ccb instead"),
"missing `composite_databar_expanded_ccb instead` migration hint: {msg}"
);
}
#[test]
fn encode_databar_expanded_cca_matches_bwip_js_separator_and_linear() {
let bm = encode_databar_expanded_cca("(01)90012345678908(3103)001750|(99)1234567").unwrap();
let want_sep: [u8; 151] = [
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1,
0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1,
0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0,
];
let want_lin: [u8; 151] = [
0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0,
1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0,
1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1,
1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1,
1, 1, 1, 0, 1, 0,
];
for x in 0..151 {
assert_eq!(u8::from(bm.get(x, 6)), want_sep[x], "sep col {x}");
assert_eq!(u8::from(bm.get(x, 7)), want_lin[x], "lin col {x}");
}
}
#[test]
fn encode_databar_expanded_ccb_accepts_cca_payload() {
let bm = encode_databar_expanded_ccb("(01)90012345678908(3103)001750|(99)1234567").unwrap();
assert_eq!(bm.width(), 151);
assert_eq!(bm.height(), 41);
}
#[test]
fn encode_databar_expanded_ccb_accepts_ccb_sized_payload() {
let bm_cca =
encode_databar_expanded_ccb("(01)90012345678908(3103)001750|(99)1234567").unwrap();
let bm_ccb = encode_databar_expanded_ccb(
"(01)90012345678908(3103)001750|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCCBFALLBACK",
)
.expect("CCB encoder must accept CCB-sized payload");
assert_eq!(
bm_ccb.width(),
bm_cca.width(),
"CCB-payload width matches CCA-payload width (linear region invariant)"
);
assert!(
bm_ccb.height() > bm_cca.height(),
"CC-B path produces more rows than CC-A for the same linear ({} vs {})",
bm_ccb.height(),
bm_cca.height()
);
}
#[test]
fn encode_gs1_128_ccc_dimensions_match_bwip_js() {
let bm = encode_gs1_128_ccc("(01)04012345123456|(99)1234567").unwrap();
assert_eq!(bm.width(), 154);
assert_eq!(bm.height(), 49);
}
#[test]
fn encode_gs1_128_ccc_matches_bwip_js_separator_and_linear() {
let bm = encode_gs1_128_ccc("(01)04012345123456|(99)1234567").unwrap();
let want_sep: [u8; 154] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1,
0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0,
0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0,
1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0,
0, 0, 1, 0, 1, 0, 0, 0, 0,
];
let want_lin: [u8; 154] = [
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0,
1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0,
1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1,
1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1,
0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1,
1, 1, 0, 1, 0, 1, 1, 0, 0,
];
for x in 0..154 {
assert_eq!(u8::from(bm.get(x, 12)), want_sep[x], "sep col {x}");
assert_eq!(u8::from(bm.get(x, 13)), want_lin[x], "lin col {x}");
}
for y in 14..49 {
for x in 0..154 {
assert_eq!(bm.get(x, y), bm.get(x, 13), "y={y} col {x}");
}
}
}
#[test]
fn encode_gs1_128_ccc_matches_bwip_js_cc_row_0_first_cells() {
let bm = encode_gs1_128_ccc("(01)04012345123456|(99)1234567").unwrap();
let want_first_30: [u8; 30] = [
1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1,
1,
];
for (x, &want) in want_first_30.iter().enumerate() {
assert_eq!(u8::from(bm.get(x, 0)), want, "y=0 col {x}");
}
}
#[test]
fn select_ccc_size_for_known_payload() {
use crate::symbology::gs1_cc::select_ccc_size;
let size = select_ccc_size(56, 145);
assert_eq!(size.columns, 5);
assert_eq!(size.eclevel, 2);
assert_eq!(size.byte_count, 10);
}
#[test]
fn databaromni_separator_finder_match_inserts_findersep() {
let mut bot = vec![0u8; 96];
for (i, &v) in DATABAROMNI_F3PAT.iter().enumerate() {
bot[18 + i] = v;
}
let sep = databaromni_separator(&bot);
for (j, &expected) in DATABAROMNI_FINDERSEP.iter().enumerate() {
assert_eq!(sep[18 + j], expected, "sep[{}] mismatch", 18 + j);
}
}
#[test]
fn apply_sepfinder_fires_at_fp64_and_misses_one_bit_flip() {
let mut bot = vec![0u8; 96];
for (i, &v) in DATABAROMNI_F3PAT.iter().enumerate() {
bot[64 + i] = v;
}
let sep = databaromni_separator(&bot);
for (j, &expected) in DATABAROMNI_FINDERSEP.iter().enumerate() {
assert_eq!(sep[64 + j], expected, "fp=64 sep[{}] mismatch", 64 + j);
}
assert_eq!(sep[73], 0, "fp=64 j=9 must be overwritten 1→0");
assert_eq!(sep[74], 1, "fp=64 j=10 must be overwritten 0→1");
let mut bot = vec![0u8; 96];
for (i, &v) in DATABAROMNI_F3PAT.iter().enumerate() {
bot[18 + i] = v;
}
bot[30] = 0; let sep = databaromni_separator(&bot);
assert_eq!(
sep[28], 0,
"near-miss must NOT overwrite to FINDERSEP[10]=1"
);
assert_eq!(
sep[30], 1,
"near-miss leaves per-position 1 (not FINDERSEP[12]=0)"
);
}
fn assert_matches_bwipp_logical_pixs(
bm: &crate::encoding::BitMatrix,
pixx: usize,
pixy: usize,
rowmult: &[usize],
logical_pixs: &[u8],
) {
assert_eq!(bm.width(), pixx, "width drift vs bwip-js");
assert_eq!(bm.height(), pixy, "height drift vs bwip-js");
let logical_rows = rowmult.len();
assert_eq!(logical_pixs.len(), logical_rows * pixx);
let mut physical_y = 0usize;
for (logical_row, &mult) in rowmult.iter().enumerate() {
for _ in 0..mult {
for x in 0..pixx {
let want = logical_pixs[logical_row * pixx + x];
let got = u8::from(bm.get(x, physical_y));
assert_eq!(
got, want,
"mismatch at physical row {physical_y} col {x} \
(logical row {logical_row})"
);
}
physical_y += 1;
}
}
assert_eq!(physical_y, pixy);
}
#[test]
fn encode_databartruncated_cca_matches_bwip_js_pixs() {
let bm = encode_databartruncated_cca("(01)24012345678905|(99)1234567").unwrap();
const WANT_PIXS: [u8; 500] = [
1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0,
0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1,
0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1,
1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0,
1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0,
1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0,
1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0,
1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1,
1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1,
0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1,
0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1,
0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1,
1, 0, 1, 1, 1, 0, 1,
];
assert_matches_bwipp_logical_pixs(&bm, 100, 20, &[2, 2, 2, 1, 13], &WANT_PIXS);
}
#[test]
fn encode_databartruncated_ccb_matches_bwip_js_pixs() {
let bm = encode_databartruncated_ccb(
"(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap();
const WANT_PIXS: [u8; 1400] = [
1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0,
1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1,
0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0,
0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0,
1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1,
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1,
0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0,
1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1,
0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1,
0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1,
1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1,
1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1,
0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1,
1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0,
0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1,
1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0,
1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0,
0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0,
0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0,
0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1,
0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1,
1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0,
1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0,
0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1,
0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1,
0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1,
1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1,
1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0,
1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0,
1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
];
let mut rowmult = vec![2usize; 12];
rowmult.push(1);
rowmult.push(13);
assert_matches_bwipp_logical_pixs(&bm, 100, 38, &rowmult, &WANT_PIXS);
}
#[test]
fn encode_databarstacked_cca_matches_bwip_js_pixs() {
let bm = encode_databarstacked_cca("(01)24012345678905|(99)1234567").unwrap();
#[rustfmt::skip]
const WANT_PIXS: [u8; 504] = [
0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0,
1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0,
1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0,
1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0,
1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0,
1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1,
1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0,
];
assert_matches_bwipp_logical_pixs(&bm, 56, 24, &[2, 2, 2, 2, 2, 1, 5, 1, 7], &WANT_PIXS);
}
#[test]
fn encode_databarstacked_ccb_matches_bwip_js_pixs() {
let bm = encode_databarstacked_ccb(
"(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap();
#[rustfmt::skip]
const WANT_PIXS: [u8; 1344] = [
0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0,
1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0,
1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1,
0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1,
0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1,
0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1,
0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1,
0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0,
1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1,
0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0,
1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1,
0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0,
1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1,
0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0,
1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1,
0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0,
1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0,
1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1,
1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0,
];
let mut rowmult = vec![2usize; 20];
rowmult.push(1);
rowmult.push(5);
rowmult.push(1);
rowmult.push(7);
assert_matches_bwipp_logical_pixs(&bm, 56, 54, &rowmult, &WANT_PIXS);
}
#[test]
fn encode_databarstackedomni_cca_matches_bwip_js_pixs() {
let bm = encode_databarstackedomni_cca("(01)24012345678905|(99)1234567").unwrap();
#[rustfmt::skip]
const WANT_PIXS: [u8; 616] = [
0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0,
];
assert_matches_bwipp_logical_pixs(
&bm,
56,
80,
&[2, 2, 2, 2, 2, 1, 33, 1, 1, 1, 33],
&WANT_PIXS,
);
}
#[test]
fn encode_databarstackedomni_ccb_matches_bwip_js_pixs() {
let bm = encode_databarstackedomni_ccb(
"(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap();
#[rustfmt::skip]
const WANT_PIXS: [u8; 1456] = [
0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1,
0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1,
0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1,
0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1,
0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1,
0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1,
0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1,
0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1,
0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1,
0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0,
];
let mut rowmult = vec![2usize; 20];
rowmult.push(1);
rowmult.push(33);
rowmult.push(1);
rowmult.push(1);
rowmult.push(1);
rowmult.push(33);
assert_matches_bwipp_logical_pixs(&bm, 56, 110, &rowmult, &WANT_PIXS);
}
#[test]
fn encode_databarexpandedstacked_cca_matches_bwip_js_pixs() {
let bm = encode_databarexpandedstacked_cca("(01)90012345678908(3103)001750|(99)1234567")
.unwrap();
assert_eq!(bm.width(), 102);
assert_eq!(bm.height(), 78);
#[rustfmt::skip]
const WANT_PIXS: [u8; 918] = [
0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0,
0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0,
0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1,
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
for logical_row in 0..7 {
let physical_y = match logical_row {
0 => 0,
1 => 2,
2 => 4,
3 => 6, 4 => 7, 5 => 41, 6 => 42, _ => unreachable!(),
};
for x in 0..102 {
let want = WANT_PIXS[logical_row * 102 + x];
let got = u8::from(bm.get(x, physical_y));
assert_eq!(
got, want,
"logical row {logical_row} (physical y={physical_y}) col {x}"
);
}
}
}
#[test]
fn encode_databarexpandedstacked_ccb_dims_match_bwip_js() {
let bm = encode_databarexpandedstacked_ccb(
"(01)90012345678908(3103)001750|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap();
assert_eq!(bm.width(), 102);
assert_eq!(bm.height(), 96);
}
#[test]
fn encode_databarexpandedstacked_cca_rejects_ccb_payload() {
let err = encode_databarexpandedstacked_cca(
"(01)90012345678908(3103)001750|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("composite_databar_expanded_stacked_cca:"),
"missing handler prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing predicate: {msg}"
);
assert!(
msg.contains("composite_databar_expanded_stacked_ccb"),
"missing CC-B replacement hint: {msg}"
);
assert!(
!msg.contains("composite_databar_stacked_cca:")
&& !msg.contains("composite_databar_stacked_omni_cca:"),
"cross-handler contamination: {msg}"
);
}
#[test]
fn encode_databarstackedomni_cca_rejects_ccb_payload() {
let err = encode_databarstackedomni_cca(
"(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("composite_databar_stacked_omni_cca:"),
"missing handler prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing predicate: {msg}"
);
assert!(
msg.contains("composite_databar_stacked_omni_ccb"),
"missing CC-B replacement hint: {msg}"
);
assert!(
!msg.contains("composite_databar_expanded_stacked_cca:")
&& !msg.contains("composite_databar_stacked_cca:"),
"cross-handler contamination: {msg}"
);
}
#[test]
fn encode_databarstacked_cca_rejects_ccb_payload() {
let err = encode_databarstacked_cca(
"(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("composite_databar_stacked_cca:"),
"missing handler prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing predicate: {msg}"
);
assert!(
msg.contains("composite_databar_stacked_ccb"),
"missing CC-B replacement hint: {msg}"
);
assert!(
!msg.contains("composite_databar_expanded_stacked_cca:")
&& !msg.contains("composite_databar_stacked_omni_cca:"),
"cross-handler contamination: {msg}"
);
}
#[test]
fn encode_databartruncated_cca_rejects_ccb_payload() {
let err = encode_databartruncated_cca(
"(01)24012345678905|(10)BATCH(21)SERIAL1234567(91)EXTRADATAFORCC",
)
.unwrap_err();
assert!(
matches!(err, crate::error::Error::InvalidData(_)),
"expected InvalidData, got {err:?}"
);
let msg = format!("{err}");
assert!(
msg.contains("composite_databar_truncated_cca:"),
"missing handler prefix: {msg}"
);
assert!(
msg.contains("payload requires CC-B"),
"missing predicate: {msg}"
);
assert!(
msg.contains("composite_databar_truncated_ccb"),
"missing CC-B replacement hint: {msg}"
);
assert!(
!msg.contains("composite_databar_omni_cca:"),
"cross-handler contamination — truncated reject leaked omni prefix: {msg}"
);
}
#[test]
fn split_composite_input_branches() {
assert_eq!(
split_composite_input("LINEAR|COMP").unwrap(),
("LINEAR", "COMP")
);
assert_eq!(split_composite_input("a|b").unwrap(), ("a", "b"));
for (input, scenario) in [
("LINEARCOMP", "no pipe"),
("|COMP", "empty linear half"),
("LINEAR|", "empty comp half"),
("|", "both halves empty"),
("", "empty input"),
] {
let err = split_composite_input(input).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("split_composite_input({input:?}, {scenario}) must yield InvalidData; got other variant");
};
assert!(
msg.contains("composite:")
&& msg.contains("LINEAR|COMP")
&& msg.contains("both non-empty"),
"{input:?} ({scenario}) must pin symbology tag + format hint + non-empty \
requirement; got {msg:?}"
);
}
assert_eq!(split_composite_input("A|B|C").unwrap(), ("A", "B|C"));
}
#[test]
fn sbs_to_pixels_alternates_bar_space() {
assert_eq!(sbs_to_pixels(&[3]), vec![1, 1, 1]);
assert_eq!(sbs_to_pixels(&[2, 3]), vec![1, 1, 0, 0, 0]);
assert_eq!(sbs_to_pixels(&[1, 2, 1]), vec![1, 0, 0, 1]);
assert_eq!(sbs_to_pixels(&[]), Vec::<u8>::new());
assert_eq!(sbs_to_pixels(&[0, 0, 1]), vec![1]);
}
#[test]
fn apply_sepfinder_f3pat_override_writes_findersep() {
let bot: [u8; 13] = DATABAROMNI_F3PAT;
let mut sep = [9u8; 13];
apply_sepfinder(&bot, &mut sep, 0);
assert_eq!(
sep, DATABAROMNI_FINDERSEP,
"F3PAT match must overwrite with FINDERSEP"
);
let mut bot_mismatch = DATABAROMNI_F3PAT;
bot_mismatch[0] = 0; let mut sep_alt = [0u8; 13];
apply_sepfinder(&bot_mismatch, &mut sep_alt, 0);
assert_ne!(
sep_alt, DATABAROMNI_FINDERSEP,
"non-match must keep the 3-branch output, not FINDERSEP"
);
}
#[test]
fn apply_databarexpanded_sepfinder_three_branch_trace() {
let bot: [u8; 5] = [0, 0, 1, 0, 0];
let mut sep = [0u8; 5];
apply_databarexpanded_sepfinder(&bot, &mut sep, 0);
assert_eq!(sep, [1, 0, 0, 1, 0]);
let bot_all_bar: [u8; 4] = [1, 1, 1, 1];
let mut sep_b = [9u8; 4];
apply_databarexpanded_sepfinder(&bot_all_bar, &mut sep_b, 0);
assert_eq!(sep_b, [0, 0, 0, 0]);
let bot_zeros: [u8; 4] = [0, 0, 0, 0];
let mut sep_z = [0u8; 4];
apply_databarexpanded_sepfinder(&bot_zeros, &mut sep_z, 0);
assert_eq!(sep_z, [1, 0, 1, 0]);
}
#[test]
fn databarexpanded_bot_alternates_starting_with_one() {
assert_eq!(databarexpanded_bot(&[]), Vec::<u8>::new());
assert_eq!(
databarexpanded_bot(&[3, 2, 1, 2]),
vec![1, 1, 1, 0, 0, 1, 0, 0]
);
assert_eq!(databarexpanded_bot(&[1]), vec![1]);
assert_eq!(databarexpanded_bot(&[1, 1]), vec![1, 0]);
assert_eq!(databarexpanded_bot(&[0, 1, 0, 1]), vec![0, 0]);
assert_eq!(
databarexpanded_bot(&[2, 2, 2, 2]),
vec![1, 1, 0, 0, 1, 1, 0, 0]
);
}
#[test]
fn ean_guard_rows_3_row_pattern() {
let pixx = 101;
let linpad_len = 4;
let linwidth = 95;
let rows = ean_guard_rows(pixx, linpad_len, linwidth);
assert_eq!(rows.len(), 3);
for r in &rows {
assert_eq!(r.len(), pixx);
}
for (i, &v) in rows[0].iter().enumerate() {
let want = if i == 5 || i == 99 { 1 } else { 0 };
assert_eq!(v, want, "row_a[{i}]");
}
for (i, &v) in rows[1].iter().enumerate() {
let want = if i == 4 || i == 100 { 1 } else { 0 };
assert_eq!(v, want, "row_b[{i}]");
}
assert_eq!(rows[2], rows[0], "row_c is row_a clone");
assert_ne!(rows[0], rows[1], "row_a and row_b must differ");
}
#[test]
fn databarlimited_linpixs_alternates_with_leading_zero() {
let mut linsbs = [1u8; 46];
let linpixs = databarlimited_linpixs(&linsbs);
assert_eq!(linpixs.len(), 46);
assert_eq!(linpixs[0], 0, "leading zero");
for i in 0..45 {
let want = if i % 2 == 0 { 1 } else { 0 };
assert_eq!(
linpixs[i + 1],
want,
"alternation at index {} (i={i})",
i + 1
);
}
linsbs[45] = 9;
let linpixs2 = databarlimited_linpixs(&linsbs);
assert_eq!(linpixs2.len(), 46, "[..45] ignores trailing");
assert_eq!(linpixs, linpixs2);
let mut linsbs = [1u8; 46];
linsbs[0] = 2;
let linpixs = databarlimited_linpixs(&linsbs);
assert_eq!(linpixs.len(), 47);
assert_eq!(linpixs[0], 0);
assert_eq!(linpixs[1], 1, "first bit of width-2 bar");
assert_eq!(linpixs[2], 1, "second bit of width-2 bar");
assert_eq!(linpixs[3], 0, "next: bit=0 (space)");
assert_eq!(linpixs[4], 1, "next: bit=1 (bar)");
}
#[test]
fn sbs_to_pixels_alternates_bar_first_with_run_expansion() {
assert!(sbs_to_pixels(&[]).is_empty(), "empty sbs");
assert_eq!(sbs_to_pixels(&[1]), vec![1], "single bar [1]");
assert_eq!(
sbs_to_pixels(&[2]),
vec![1, 1],
"single bar [2] expands to 2 cells"
);
assert_eq!(
sbs_to_pixels(&[1, 1]),
vec![1, 0],
"[1, 1] alternates bar then space"
);
assert_eq!(
sbs_to_pixels(&[3, 2]),
vec![1, 1, 1, 0, 0],
"[3, 2] = 3 bar + 2 space"
);
assert_eq!(
sbs_to_pixels(&[1, 1, 1]),
vec![1, 0, 1],
"[1, 1, 1] bar-space-bar"
);
assert_eq!(
sbs_to_pixels(&[0, 2]),
vec![0, 0],
"[0, 2]: zero-width bar (no write) → space writes 2 zeros"
);
assert_eq!(
sbs_to_pixels(&[2, 0, 3]),
vec![1, 1, 1, 1, 1],
"[2, 0, 3]: zero-width space writes nothing but parity \
still toggles → next entry is bar again"
);
let cases: &[&[u32]] = &[&[1, 2, 3, 4], &[5, 5, 5], &[1, 0, 1, 0, 1], &[10, 20, 30]];
for sbs in cases {
let pixs = sbs_to_pixels(sbs);
let total: u32 = sbs.iter().sum();
assert_eq!(
pixs.len() as u32,
total,
"sbs={sbs:?}: len(pixs) should equal sum(sbs)"
);
}
}
#[test]
fn databarstacked_composite_separator_invert_and_edge_zero() {
let top = [1u8; 50];
let sep = databarstacked_composite_separator(&top);
assert_eq!(sep.len(), 50);
assert!(
sep.iter().all(|&v| v == 0),
"all-1 top must invert to all-0 (no sepfinder override)"
);
let top = [0u8; 50];
let sep = databarstacked_composite_separator(&top);
assert_eq!(sep.len(), 50);
assert_eq!(&sep[..4], &[0, 0, 0, 0], "first 4 forced 0");
assert_eq!(&sep[46..], &[0, 0, 0, 0], "last 4 (46..50) forced 0");
assert!(
sep[4..18].iter().all(|&v| v == 1),
"4..18 region preserves invert: 0→1"
);
assert!(
sep[31..46].iter().all(|&v| v == 1),
"31..46 region preserves invert: 0→1"
);
let mut top = [0u8; 50];
top[10] = 1;
let sep = databarstacked_composite_separator(&top);
assert_eq!(
sep[10], 0,
"top[10]=1 → sep[10]=0 (kills `1 - v` → `v` identity)"
);
assert_eq!(sep[9], 1);
assert_eq!(sep[11], 1);
let top = [0u8; 50];
let sep = databarstacked_composite_separator(&top);
let expected_window: [u8; 13] = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0];
assert_eq!(
&sep[18..=30],
&expected_window,
"sepfinder at fp=18 must write alternating pattern starting with 0 \
at sep[18]; a mutant on fp would shift this pattern"
);
assert_eq!(
sep[18], 0,
"sep[18] (sepfinder window start) must be 0 for all-0s top"
);
assert_eq!(sep[30], 0, "sep[30] (sepfinder window end, i=12) must be 0");
}
#[test]
fn sbs_to_pixels_starts_with_bar_and_alternates_per_width() {
assert!(
sbs_to_pixels(&[]).is_empty(),
"empty sbs → empty pixel sequence"
);
assert_eq!(
sbs_to_pixels(&[1]),
vec![1],
"single bar width 1 → [1] (pins is_bar=true initial)"
);
assert_eq!(
sbs_to_pixels(&[1, 1]),
vec![1, 0],
"[1, 1] → [1, 0] (starts bar + alternates)"
);
assert_eq!(
sbs_to_pixels(&[2, 3, 1]),
vec![1, 1, 0, 0, 0, 1],
"[2, 3, 1] → 2 bars + 3 spaces + 1 bar"
);
assert_eq!(
sbs_to_pixels(&[3, 1, 2, 1]),
vec![1, 1, 1, 0, 1, 1, 0],
"[3, 1, 2, 1] → 7-pixel BSBS alternation"
);
assert_eq!(
sbs_to_pixels(&[5]),
vec![1, 1, 1, 1, 1],
"single bar width 5 → 5 ones (pins `0..w` loop)"
);
assert_eq!(
sbs_to_pixels(&[0, 1]),
vec![0],
"[0, 1] → zero-width bar skipped, 1 space (alternation toggles)"
);
let zero_width_pixels = sbs_to_pixels(&[0]);
assert!(
zero_width_pixels.is_empty(),
"sbs_to_pixels(&[0]) (single zero-width bar) must emit no pixels (and not panic); got len={}",
zero_width_pixels.len()
);
let cases: &[&[u32]] = &[
&[1],
&[1, 1],
&[2, 2, 2, 2],
&[7, 3, 5, 1, 4],
&[10, 1, 10, 1, 10],
&[0, 5, 0, 3, 0, 1],
];
for &sbs in cases {
let out = sbs_to_pixels(sbs);
let total: u32 = sbs.iter().sum();
assert_eq!(
out.len(),
total as usize,
"sbs {sbs:?}: output length must equal sum of widths"
);
assert!(
out.iter().all(|&b| b == 0 || b == 1),
"sbs {sbs:?}: every pixel must be 0 or 1"
);
let mut idx = 0;
let mut expected_bar = true;
for &w in sbs {
for _ in 0..w {
let expected = if expected_bar { 1 } else { 0 };
assert_eq!(
out[idx], expected,
"sbs {sbs:?} at idx {idx}: expected {expected}"
);
idx += 1;
}
expected_bar = !expected_bar;
}
}
}
#[test]
fn split_composite_input_pipe_separated_with_non_empty_halves() {
let (l, c) = split_composite_input("A|B").unwrap();
assert_eq!((l, c), ("A", "B"), "A|B → (A, B)");
let (l, c) = split_composite_input("1234|56789").unwrap();
assert_eq!((l, c), ("1234", "56789"));
let (l, c) = split_composite_input("(01)90012345678908|comp_data").unwrap();
assert_eq!((l, c), ("(01)90012345678908", "comp_data"));
let (l, c) = split_composite_input("a|b|c").unwrap();
assert_eq!((l, c), ("a", "b|c"));
let (l, c) = split_composite_input("a||b").unwrap();
assert_eq!((l, c), ("a", "|b"));
for (input, scenario) in [
("|abc", "empty linear half"),
("abc|", "empty comp half"),
("|", "both halves empty"),
("abc", "no separator"),
("", "empty input"),
] {
let err = split_composite_input(input).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("split_composite_input({input:?}, {scenario}) must yield InvalidData; got other variant");
};
assert!(
msg.contains("composite:"),
"diagnostic for {input:?} ({scenario}) must carry the symbology tag; got {msg:?}"
);
assert!(
msg.contains("LINEAR|COMP"),
"diagnostic for {input:?} ({scenario}) must show the expected format; got {msg:?}"
);
assert!(
msg.contains("both non-empty"),
"diagnostic for {input:?} ({scenario}) must mention the non-empty requirement; got {msg:?}"
);
}
let input = String::from("X|YZ");
let (l, c) = split_composite_input(&input).unwrap();
assert_eq!(l, "X");
assert_eq!(c, "YZ");
}
#[test]
fn databarexpanded_separator_margins_and_complement_pin() {
let linsbs = [1u8; 25];
let sep = databarexpanded_separator(&linsbs);
let expected = vec![
0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0,
];
assert_eq!(sep, expected, "databarexpanded_separator layout broken");
assert_eq!(sep.len(), 25, "length must equal bot length");
for i in 0..3 {
assert_eq!(sep[i], 0, "sep[{i}] (left margin) must be 0");
}
for i in 21..25 {
assert_eq!(sep[i], 0, "sep[{i}] (right margin) must be 0");
}
assert_eq!(DATABAREXPANDED_SEPLEFT, 3);
assert_eq!(DATABAREXPANDED_SEPRIGHT, 4);
}
#[test]
fn build_ean_cca_composite_fingerprint_pinned() {
fn fp_bm(bm: &crate::encoding::BitMatrix) -> (usize, usize, u64) {
let w = bm.width();
let h = bm.height();
let mut s: u64 = 0;
for y in 0..h {
for x in 0..w {
let v = u64::from(bm.get(x, y));
let idx = (y as u64) * (w as u64) + (x as u64);
s = s.wrapping_add(
v.wrapping_mul(idx.wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
}
(w, h, s)
}
let cases: &[(&str, &str, (usize, usize, u64))] = &[
("ean13", "5901234123457|(99)1234567", FP_EAN13),
("upca", "012345678905|(99)1234567", FP_UPCA),
("ean8", "12345670|(99)1234567", FP_EAN8),
("upce", "0123456|(99)1234567", FP_UPCE),
];
for (tag, input, want) in cases {
let bm = match *tag {
"ean13" => encode_ean13_cca(input),
"upca" => encode_upca_cca(input),
"ean8" => encode_ean8_cca(input),
"upce" => encode_upce_cca(input),
_ => unreachable!(),
}
.unwrap_or_else(|e| panic!("encode({tag}, {input}) ok: {e:?}"));
let got = fp_bm(&bm);
assert_eq!(got, *want, "fingerprint changed for {tag}");
}
}
const FP_EAN13: (usize, usize, u64) = (99, 84, 44769445447014139);
const FP_UPCA: (usize, usize, u64) = (99, 84, 40243451972877391);
const FP_EAN8: (usize, usize, u64) = (72, 86, 22274251743223652);
const FP_UPCE: (usize, usize, u64) = (55, 88, 16648087551404039);
#[test]
fn build_gs1_128_ccc_composite_fingerprint_pinned() {
fn fp_bm(bm: &crate::encoding::BitMatrix) -> (usize, usize, u64) {
let w = bm.width();
let h = bm.height();
let mut s: u64 = 0;
for y in 0..h {
for x in 0..w {
let v = u64::from(bm.get(x, y));
let idx = (y as u64) * (w as u64) + (x as u64);
s = s.wrapping_add(
v.wrapping_mul(idx.wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
}
(w, h, s)
}
let cases: &[(&str, (usize, usize, u64))] = &[
("(01)04012345123456|(99)1234567", FP_CCC_TINY),
("(01)04012345123456|(10)BATCH", FP_CCC_SHORT),
("(01)04012345123456|(10)BATCH(21)SERIAL1", FP_CCC_MED),
(
"(01)04012345123456|(10)BATCH(21)SERIAL1234567",
FP_CCC_LARGE,
),
("(00)123456789012345678|(99)1234567", FP_CCC_SSCC),
("(01)04012345123456|(10)B(21)S(91)X", FP_CCC_MIXED),
];
for (input, want) in cases {
let bm = encode_gs1_128_ccc(input)
.unwrap_or_else(|e| panic!("encode_gs1_128_ccc({input:?}) ok: {e:?}"));
let got = fp_bm(&bm);
assert_eq!(got, *want, "fingerprint changed for {input:?}");
}
}
const FP_CCC_TINY: (usize, usize, u64) = (154, 49, 41054403333348979);
const FP_CCC_SHORT: (usize, usize, u64) = (154, 49, 41166693929346562);
const FP_CCC_MED: (usize, usize, u64) = (154, 52, 46408278159240973);
const FP_CCC_LARGE: (usize, usize, u64) = (154, 52, 46333534557082735);
const FP_CCC_SSCC: (usize, usize, u64) = (174, 46, 42996062040497976);
const FP_CCC_MIXED: (usize, usize, u64) = (154, 49, 41247680764414672);
#[test]
fn build_databar_expanded_composite_and_stacked_separator_fingerprint_pinned() {
fn fp_bm(bm: &crate::encoding::BitMatrix) -> (usize, usize, u64) {
let w = bm.width();
let h = bm.height();
let mut s: u64 = 0;
for y in 0..h {
for x in 0..w {
let v = u64::from(bm.get(x, y));
let idx = (y as u64) * (w as u64) + (x as u64);
s = s.wrapping_add(
v.wrapping_mul(idx.wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
}
(w, h, s)
}
let expanded_cases: &[(&str, (usize, usize, u64))] = &[
("(01)90012345678908(3103)001750|(99)1234567", FP_EXP_LONG),
("(01)90012345678908|(99)1234567", FP_EXP_SHORT),
("(10)BATCH123|(99)9876543", FP_EXP_BATCH),
];
for (input, want) in expanded_cases {
let bm = encode_databar_expanded_cca(input)
.unwrap_or_else(|e| panic!("encode_databar_expanded_cca({input:?}) ok: {e:?}"));
let got = fp_bm(&bm);
assert_eq!(got, *want, "fingerprint changed for expanded {input:?}");
}
let stacked_cases: &[(&str, (usize, usize, u64))] =
&[("(01)90012345678908(3103)001750|(99)1234567", FP_EXP_STACKED)];
for (input, want) in stacked_cases {
let bm = encode_databarexpandedstacked_cca(input).unwrap_or_else(|e| {
panic!("encode_databarexpandedstacked_cca({input:?}) ok: {e:?}")
});
let got = fp_bm(&bm);
assert_eq!(got, *want, "fingerprint changed for stacked {input:?}");
}
}
const FP_EXP_LONG: (usize, usize, u64) = (151, 41, 27323867178882543);
const FP_EXP_SHORT: (usize, usize, u64) = (134, 41, 21624249440684538);
const FP_EXP_BATCH: (usize, usize, u64) = (183, 41, 44226467339922784);
const FP_EXP_STACKED: (usize, usize, u64) = (102, 78, 29875785976356962);
#[test]
fn build_ean_cca_composite_state_machine_fingerprint_pinned() {
fn fp_bm(bm: &crate::encoding::BitMatrix) -> (usize, usize, u64) {
let w = bm.width();
let h = bm.height();
let mut s: u64 = 0;
for y in 0..h {
for x in 0..w {
let v = u64::from(bm.get(x, y));
let idx = (y as u64) * (w as u64) + (x as u64);
s = s.wrapping_add(
v.wrapping_mul(idx.wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
}
(w, h, s)
}
fn make_cc(width: usize, rows: usize) -> crate::encoding::BitMatrix {
let mut bm = crate::encoding::BitMatrix::new(width, rows);
for y in 0..rows {
for x in 0..width {
bm.set(x, y, (x + 3 * y) % 2 == 0);
}
}
bm
}
fn make_linsbs(linwidth: usize) -> Vec<u32> {
vec![1u32; linwidth]
}
let cases: &[(&str, usize, usize, usize, usize, (usize, usize, u64))] = &[
("trail0_lw95_cc98", 95, 98, 3, EAN_LINHEIGHT, FP_BECA_A),
("ean13_lw95_cc99", 95, 99, 3, EAN_LINHEIGHT, FP_BECA_B),
("upce_lw51_cc55", 51, 55, 3, EAN_LINHEIGHT, FP_BECA_C),
("ccb_wide_lw51_cc99", 51, 99, 10, EAN_LINHEIGHT, FP_BECA_D),
("ccb_tall_lw95_cc99", 95, 99, 17, EAN_LINHEIGHT, FP_BECA_E),
];
for (tag, linwidth, ccpixx, cc_rows, linheight, want) in cases {
let cc = make_cc(*ccpixx, *cc_rows);
let linsbs = make_linsbs(*linwidth);
let bm = build_ean_cca_composite(&cc, &linsbs, *linwidth, *linheight);
let got = fp_bm(&bm);
eprintln!("CAP build_ean_cca/{tag} -> {got:?}");
assert_eq!(got, *want, "fingerprint changed for {tag}");
}
}
const FP_BECA_A: (usize, usize, u64) = (98, 84, 43420224948491210);
const FP_BECA_B: (usize, usize, u64) = (99, 84, 43871083516932821);
const FP_BECA_C: (usize, usize, u64) = (55, 84, 13214627983265759);
const FP_BECA_D: (usize, usize, u64) = (99, 98, 33290709619576179);
const FP_BECA_E: (usize, usize, u64) = (99, 112, 76678993742555276);
#[test]
fn build_gs1_128_ccc_composite_state_machine_fingerprint_pinned() {
fn fp_bm(bm: &crate::encoding::BitMatrix) -> (usize, usize, u64) {
let w = bm.width();
let h = bm.height();
let mut s: u64 = 0;
for y in 0..h {
for x in 0..w {
let v = u64::from(bm.get(x, y));
let idx = (y as u64) * (w as u64) + (x as u64);
s = s.wrapping_add(
v.wrapping_mul(idx.wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
}
(w, h, s)
}
fn make_cc(width: usize, rows: usize) -> crate::encoding::BitMatrix {
let mut bm = crate::encoding::BitMatrix::new(width, rows);
for y in 0..rows {
for x in 0..width {
bm.set(x, y, (2 * x + y) % 3 == 0);
}
}
bm
}
fn make_linsbs(linwidth: usize) -> Vec<u32> {
vec![1u32; linwidth]
}
let cases: &[(&str, usize, usize, usize, usize, (usize, usize, u64))] = &[
(
"ccrpad_lw160_cw150",
160,
150,
5,
GS1_128_LINHEIGHT,
FP_BCCC_I,
),
(
"boundary_lw150_cw157",
150,
157,
5,
GS1_128_LINHEIGHT,
FP_BCCC_J,
),
(
"linrpad_lw140_cw160",
140,
160,
5,
GS1_128_LINHEIGHT,
FP_BCCC_K,
),
(
"linrpad_big_lw80_cw120",
80,
120,
6,
GS1_128_LINHEIGHT,
FP_BCCC_L,
),
(
"ccrpad_big_lw220_cw200",
220,
200,
8,
GS1_128_LINHEIGHT,
FP_BCCC_M,
),
(
"ccrpad_wide_lw200_cw100",
200,
100,
4,
GS1_128_LINHEIGHT,
FP_BCCC_N,
),
(
"tall_lw100_cw110",
100,
110,
12,
GS1_128_LINHEIGHT,
FP_BCCC_O,
),
];
for (tag, linwidth, cc_width, cc_rows, linheight, want) in cases {
let cc = make_cc(*cc_width, *cc_rows);
let linsbs = make_linsbs(*linwidth);
let bm = build_gs1_128_ccc_composite(&cc, &linsbs, *linheight);
let got = fp_bm(&bm);
eprintln!("CAP build_gs1_128_ccc/{tag} -> {got:?}");
assert_eq!(got, *want, "fingerprint changed for {tag}");
}
}
const FP_BCCC_I: (usize, usize, u64) = (167, 52, 46461611082550985);
const FP_BCCC_J: (usize, usize, u64) = (157, 52, 41221574388705237);
const FP_BCCC_K: (usize, usize, u64) = (160, 52, 39377973193003341);
const FP_BCCC_L: (usize, usize, u64) = (120, 55, 19221194055122760);
const FP_BCCC_M: (usize, usize, u64) = (227, 61, 115793660180246222);
const FP_BCCC_N: (usize, usize, u64) = (207, 49, 63310888938024258);
const FP_BCCC_O: (usize, usize, u64) = (110, 73, 36378511217352800);
#[test]
fn ean_guard_rows_narrow_ccpixx_does_not_panic() {
fn cc(width: usize, rows: usize) -> crate::encoding::BitMatrix {
let mut bm = crate::encoding::BitMatrix::new(width, rows);
for y in 0..rows {
for x in 0..width {
bm.set(x, y, (x + y) % 2 == 0);
}
}
bm
}
let narrow = build_ean_cca_composite(&cc(94, 3), &vec![1u32; 95], 95, EAN_LINHEIGHT);
assert!(narrow.width() > 0 && narrow.height() > 0);
let boundary = build_ean_cca_composite(&cc(96, 3), &vec![1u32; 95], 95, EAN_LINHEIGHT);
assert!(boundary.width() > 0 && boundary.height() > 0);
let prod = build_ean_cca_composite(&cc(99, 3), &vec![1u32; 95], 95, EAN_LINHEIGHT);
assert_eq!(prod.width(), 99);
}
fn sep_wsum(s: &[u8]) -> u64 {
s.iter()
.enumerate()
.map(|(i, &v)| (v as u64) * ((i as u64) + 1))
.sum()
}
#[test]
fn databarexpandedstacked_separator_stride_and_bound_mutants_killed() {
fn synth(n: usize) -> Vec<u8> {
(0..n).map(|i| ((i / 3) % 2) as u8).collect()
}
let cases: &[(usize, u64)] = &[
(130, 3802), (200, 8868), (82, 1398),
(180, 7345),
];
for &(n, want) in cases {
let sep = databarexpandedstacked_composite_separator(&synth(n));
assert_eq!(sep.len(), n, "separator preserves length (n={n})");
assert_eq!(
sep_wsum(&sep),
want,
"stacked separator fingerprint changed for n={n} \
(would catch a finder-loop stride/bound mutant)"
);
}
}
#[test]
fn apply_sepfinder_prevbot_and_match_bound_mutants_killed() {
let bot = [1u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let mut sep = vec![0u8; bot.len()];
sep[0] = 1; apply_sepfinder(&bot, &mut sep, 1);
assert_eq!(
sep[1], 1,
"apply_sepfinder must use bot[i-1] (L1017 `>`/`-` mutants give 0)"
);
assert_eq!(
sep,
vec![1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
"full sep recurrence pins the prev-bot path"
);
let bot12: Vec<u8> = DATABAROMNI_F3PAT[..12].to_vec();
let mut sep12: Vec<u8> = bot12.iter().map(|&b| 1 - b).collect();
apply_sepfinder(&bot12, &mut sep12, 0);
assert_eq!(
sep12,
vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
"12-cell f3pat-prefix bot: no override, windowed sep only \
(the L1032 `<=` mutant panics on bot[12] here)"
);
}
#[test]
fn apply_databarexpanded_sepfinder_prevbot_mutants_killed() {
let bot = [1u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let mut sep = vec![0u8; bot.len()];
sep[0] = 1;
apply_databarexpanded_sepfinder(&bot, &mut sep, 1);
assert_eq!(
sep[1], 1,
"apply_databarexpanded_sepfinder must use bot[i-1] \
(L1380 `>`/`-` mutants give 0)"
);
assert_eq!(sep, vec![1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]);
}
#[test]
fn gs1_128_cc_offset_a_off_grid_linwidth_kills_sign_mutant() {
assert_eq!(
gs1_128_cc_offset_a(33),
-45,
"gs1_128_cc_offset_a(33): s=(33-2)/11=2 → -45 \
(the L1613 `+` mutant computes s=3 → -34)"
);
}
#[test]
fn build_gs1_128_cca_ccrpad_mul_mutant_killed() {
fn mk_cc(w: usize, r: usize) -> crate::encoding::BitMatrix {
let mut b = crate::encoding::BitMatrix::new(w, r);
for y in 0..r {
for x in 0..w {
b.set(x, y, (x + 3 * y) % 2 == 0);
}
}
b
}
fn fp_bm(bm: &crate::encoding::BitMatrix) -> (usize, usize, u64) {
let w = bm.width();
let h = bm.height();
let mut s: u64 = 0;
for y in 0..h {
for x in 0..w {
let v = u64::from(bm.get(x, y));
let idx = (y as u64) * (w as u64) + (x as u64);
s = s.wrapping_add(
v.wrapping_mul(idx.wrapping_add(1).wrapping_mul(2_654_435_761)),
);
}
}
(w, h, s)
}
let cc = mk_cc(99, 5);
let linsbs = vec![1u32; 101]; assert_eq!(gs1_128_cc_offset_a(101), 1, "precondition: x=1 at lw=101");
let bm = build_gs1_128_cca_composite(&cc, &linsbs, GS1_128_LINHEIGHT);
assert_eq!(
fp_bm(&bm),
(101, 47, 15083406502160740),
"build_gs1_128_cca fingerprint changed (the L1644:55 `*` mutant \
widens pixx to 102)"
);
}
#[test]
fn composite_equivalence_notes() {
fn ean_cc(w: usize, r: usize) -> crate::encoding::BitMatrix {
let mut b = crate::encoding::BitMatrix::new(w, r);
for y in 0..r {
for x in 0..w {
b.set(x, y, (x + 3 * y) % 2 == 0);
}
}
b
}
for &(lw, ccpixx) in &[(95usize, 99usize), (95, 98), (95, 97), (95, 96), (51, 99)] {
let bm = build_ean_cca_composite(&ean_cc(ccpixx, 3), &vec![1u32; lw], lw, 4);
let linpad_len = ccpixx.saturating_sub(lw + 2);
let orig = lw as isize + linpad_len as isize + 1 - ccpixx as isize;
let mutated = lw as isize - linpad_len as isize + 1 - ccpixx as isize;
assert_eq!(
orig.max(0),
mutated.max(0),
"L1122 `+`→`-`: ccrpad_len invariant (lw={lw}, ccpixx={ccpixx})"
);
assert_eq!(bm.width(), ccpixx + orig.max(0) as usize);
}
let bot_f3 = DATABAROMNI_F3PAT.to_vec(); let mut sep_f3: Vec<u8> = bot_f3.iter().map(|&b| 1 - b).collect();
apply_sepfinder(&bot_f3, &mut sep_f3, 0);
assert_eq!(
sep_f3,
DATABAROMNI_FINDERSEP.to_vec(),
"f3pat match writes FINDERSEP; max index fp+12=12 < len 13, so the \
L1036 `<=` bound never reaches len (no-op mutation)"
);
fn dx_cc(w: usize, r: usize) -> crate::encoding::BitMatrix {
let mut b = crate::encoding::BitMatrix::new(w, r);
for y in 0..r {
for x in 0..w {
b.set(x, y, (x + y) % 2 == 0);
}
}
b
}
let linsbs: Vec<u8> = vec![1u8; 40];
let for_wide = build_databar_expanded_composite(&dx_cc(80, 3), &linsbs, 4);
let for_narrow = build_databar_expanded_composite(&dx_cc(20, 3), &linsbs, 4);
assert_eq!(
for_wide.width(),
41,
"pixx = linsbs_sum + 1, not diff-derived"
);
assert_eq!(
for_wide.width(),
for_narrow.width(),
"L1445 `diff` is dead; changing cc_width (hence diff) leaves width fixed"
);
fn g_cc(w: usize, r: usize) -> crate::encoding::BitMatrix {
let mut b = crate::encoding::BitMatrix::new(w, r);
for y in 0..r {
for x in 0..w {
b.set(x, y, (x + r * y) % 2 == 0);
}
}
b
}
for &lw in &[101usize, 112, 123, 145, 167, 200] {
let x = gs1_128_cc_offset_a(lw);
assert!(x >= 0, "production offset non-negative (lw={lw})");
let bm = build_gs1_128_cca_composite(&g_cc(99, 5), &vec![1u32; lw], GS1_128_LINHEIGHT);
assert_eq!(
bm.width(),
lw,
"pixx pinned to linwidth at cc_width=99 (lw={lw}); ccrpad value \
is absorbed by .max(linwidth) → L1644:34 and L1646:35 are inert"
);
}
let ccc_cases: &[(usize, usize)] = &[
(160, 150), (150, 157), (140, 160), (220, 200), (100, 110), ];
for &(lw, cw) in ccc_cases {
let diff = lw as isize + 7 - cw as isize;
let ccrpad = diff.max(0) as usize;
let linrpad = (-diff).max(0) as usize;
let want = (cw + ccrpad).max(7 + lw + linrpad);
let bm = build_gs1_128_ccc_composite(&g_cc(cw, 5), &vec![1u32; lw], GS1_128_LINHEIGHT);
assert_eq!(
bm.width(),
want,
"ccc pixx invariant (lw={lw}, cw={cw}, diff={diff}); the L1747 / \
L1752 mutants leave the dominant max-term unchanged"
);
let arg1 = cw + ccrpad;
#[allow(clippy::erasing_op)]
let arg1_mul = 0usize * cw + ccrpad;
assert_eq!(
arg1.max(7 + lw + linrpad),
arg1_mul.max(7 + lw + linrpad),
"L1752:24 `*`: first max-arg never strictly dominant"
);
let arg2 = 7 + lw + linrpad;
let arg2_sub = (7 + lw).saturating_sub(linrpad);
assert_eq!(
arg1.max(arg2),
arg1.max(arg2_sub),
"L1752:68 `-`: subtracting linrpad never lowers the max below arg1"
);
if diff == 0 {
assert_eq!((ccrpad, linrpad), (0, 0), "L1747 `>=` no-op at diff==0");
}
}
}
}