#![allow(dead_code)]
use crate::encoding::BitMatrix;
use crate::error::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub(crate) enum Format {
Full,
Micro,
Rmqr,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct VersionMetric {
pub format: Format,
pub version_str: &'static str,
pub layout_id: u8,
pub rows: u16,
pub cols: u16,
pub fimax: u16,
pub fimas: u16,
pub datacap_bits: u32,
pub eclen: [u16; 4],
pub blocks: [i8; 8],
}
pub(crate) const NA: u16 = 99;
pub(crate) const NA_I8: i8 = 99;
#[rustfmt::skip]
pub(crate) const FULL_METRICS: [VersionMetric; 76] = [
VersionMetric { format: Format::Micro, version_str: "M1", layout_id: 3, rows: 11, cols: 11, fimax: 98, fimas: 99, datacap_bits: 36, eclen: [2, 99, 99, 99], blocks: [1, 0, -1, -1, -1, -1, -1, -1] },
VersionMetric { format: Format::Micro, version_str: "M2", layout_id: 4, rows: 13, cols: 13, fimax: 98, fimas: 99, datacap_bits: 80, eclen: [5, 6, 99, 99], blocks: [1, 0, 1, 0, -1, -1, -1, -1] },
VersionMetric { format: Format::Micro, version_str: "M3", layout_id: 5, rows: 15, cols: 15, fimax: 98, fimas: 99, datacap_bits: 132, eclen: [6, 8, 99, 99], blocks: [1, 0, 1, 0, -1, -1, -1, -1] },
VersionMetric { format: Format::Micro, version_str: "M4", layout_id: 6, rows: 17, cols: 17, fimax: 98, fimas: 99, datacap_bits: 192, eclen: [8, 10, 14, 99], blocks: [1, 0, 1, 0, 1, 0, -1, -1] },
VersionMetric { format: Format::Full, version_str: "1", layout_id: 0, rows: 21, cols: 21, fimax: 98, fimas: 99, datacap_bits: 208, eclen: [7, 10, 13, 17], blocks: [1, 0, 1, 0, 1, 0, 1, 0] },
VersionMetric { format: Format::Full, version_str: "2", layout_id: 0, rows: 25, cols: 25, fimax: 18, fimas: 99, datacap_bits: 359, eclen: [10, 16, 22, 28], blocks: [1, 0, 1, 0, 1, 0, 1, 0] },
VersionMetric { format: Format::Full, version_str: "3", layout_id: 0, rows: 29, cols: 29, fimax: 22, fimas: 99, datacap_bits: 567, eclen: [15, 26, 36, 44], blocks: [1, 0, 1, 0, 2, 0, 2, 0] },
VersionMetric { format: Format::Full, version_str: "4", layout_id: 0, rows: 33, cols: 33, fimax: 26, fimas: 99, datacap_bits: 807, eclen: [20, 36, 52, 64], blocks: [1, 0, 2, 0, 2, 0, 4, 0] },
VersionMetric { format: Format::Full, version_str: "5", layout_id: 0, rows: 37, cols: 37, fimax: 30, fimas: 99, datacap_bits: 1079, eclen: [26, 48, 72, 88], blocks: [1, 0, 2, 0, 2, 2, 2, 2] },
VersionMetric { format: Format::Full, version_str: "6", layout_id: 0, rows: 41, cols: 41, fimax: 34, fimas: 99, datacap_bits: 1383, eclen: [36, 64, 96, 112], blocks: [2, 0, 4, 0, 4, 0, 4, 0] },
VersionMetric { format: Format::Full, version_str: "7", layout_id: 0, rows: 45, cols: 45, fimax: 22, fimas: 38, datacap_bits: 1568, eclen: [40, 72, 108, 130], blocks: [2, 0, 4, 0, 2, 4, 4, 1] },
VersionMetric { format: Format::Full, version_str: "8", layout_id: 0, rows: 49, cols: 49, fimax: 24, fimas: 42, datacap_bits: 1936, eclen: [48, 88, 132, 156], blocks: [2, 0, 2, 2, 4, 2, 4, 2] },
VersionMetric { format: Format::Full, version_str: "9", layout_id: 0, rows: 53, cols: 53, fimax: 26, fimas: 46, datacap_bits: 2336, eclen: [60, 110, 160, 192], blocks: [2, 0, 3, 2, 4, 4, 4, 4] },
VersionMetric { format: Format::Full, version_str: "10", layout_id: 1, rows: 57, cols: 57, fimax: 28, fimas: 50, datacap_bits: 2768, eclen: [72, 130, 192, 224], blocks: [2, 2, 4, 1, 6, 2, 6, 2] },
VersionMetric { format: Format::Full, version_str: "11", layout_id: 1, rows: 61, cols: 61, fimax: 30, fimas: 54, datacap_bits: 3232, eclen: [80, 150, 224, 264], blocks: [4, 0, 1, 4, 4, 4, 3, 8] },
VersionMetric { format: Format::Full, version_str: "12", layout_id: 1, rows: 65, cols: 65, fimax: 32, fimas: 58, datacap_bits: 3728, eclen: [96, 176, 260, 308], blocks: [2, 2, 6, 2, 4, 6, 7, 4] },
VersionMetric { format: Format::Full, version_str: "13", layout_id: 1, rows: 69, cols: 69, fimax: 34, fimas: 62, datacap_bits: 4256, eclen: [104, 198, 288, 352], blocks: [4, 0, 8, 1, 8, 4, 12, 4] },
VersionMetric { format: Format::Full, version_str: "14", layout_id: 1, rows: 73, cols: 73, fimax: 26, fimas: 46, datacap_bits: 4651, eclen: [120, 216, 320, 384], blocks: [3, 1, 4, 5, 11, 5, 11, 5] },
VersionMetric { format: Format::Full, version_str: "15", layout_id: 1, rows: 77, cols: 77, fimax: 26, fimas: 48, datacap_bits: 5243, eclen: [132, 240, 360, 432], blocks: [5, 1, 5, 5, 5, 7, 11, 7] },
VersionMetric { format: Format::Full, version_str: "16", layout_id: 1, rows: 81, cols: 81, fimax: 26, fimas: 50, datacap_bits: 5867, eclen: [144, 280, 408, 480], blocks: [5, 1, 7, 3, 15, 2, 3, 13] },
VersionMetric { format: Format::Full, version_str: "17", layout_id: 1, rows: 85, cols: 85, fimax: 30, fimas: 54, datacap_bits: 6523, eclen: [168, 308, 448, 532], blocks: [1, 5, 10, 1, 1, 15, 2, 17] },
VersionMetric { format: Format::Full, version_str: "18", layout_id: 1, rows: 89, cols: 89, fimax: 30, fimas: 56, datacap_bits: 7211, eclen: [180, 338, 504, 588], blocks: [5, 1, 9, 4, 17, 1, 2, 19] },
VersionMetric { format: Format::Full, version_str: "19", layout_id: 1, rows: 93, cols: 93, fimax: 30, fimas: 58, datacap_bits: 7931, eclen: [196, 364, 546, 650], blocks: [3, 4, 3, 11, 17, 4, 9, 16] },
VersionMetric { format: Format::Full, version_str: "20", layout_id: 1, rows: 97, cols: 97, fimax: 34, fimas: 62, datacap_bits: 8683, eclen: [224, 416, 600, 700], blocks: [3, 5, 3, 13, 15, 5, 15, 10] },
VersionMetric { format: Format::Full, version_str: "21", layout_id: 1, rows: 101, cols: 101, fimax: 28, fimas: 50, datacap_bits: 9252, eclen: [224, 442, 644, 750], blocks: [4, 4, 17, 0, 17, 6, 19, 6] },
VersionMetric { format: Format::Full, version_str: "22", layout_id: 1, rows: 105, cols: 105, fimax: 26, fimas: 50, datacap_bits: 10068, eclen: [252, 476, 690, 816], blocks: [2, 7, 17, 0, 7, 16, 34, 0] },
VersionMetric { format: Format::Full, version_str: "23", layout_id: 1, rows: 109, cols: 109, fimax: 30, fimas: 54, datacap_bits: 10916, eclen: [270, 504, 750, 900], blocks: [4, 5, 4, 14, 11, 14, 16, 14] },
VersionMetric { format: Format::Full, version_str: "24", layout_id: 1, rows: 113, cols: 113, fimax: 28, fimas: 54, datacap_bits: 11796, eclen: [300, 560, 810, 960], blocks: [6, 4, 6, 14, 11, 16, 30, 2] },
VersionMetric { format: Format::Full, version_str: "25", layout_id: 1, rows: 117, cols: 117, fimax: 32, fimas: 58, datacap_bits: 12708, eclen: [312, 588, 870, 1050], blocks: [8, 4, 8, 13, 7, 22, 22, 13] },
VersionMetric { format: Format::Full, version_str: "26", layout_id: 1, rows: 121, cols: 121, fimax: 30, fimas: 58, datacap_bits: 13652, eclen: [336, 644, 952, 1110], blocks: [10, 2, 19, 4, 28, 6, 33, 4] },
VersionMetric { format: Format::Full, version_str: "27", layout_id: 2, rows: 125, cols: 125, fimax: 34, fimas: 62, datacap_bits: 14628, eclen: [360, 700, 1020, 1200], blocks: [8, 4, 22, 3, 8, 26, 12, 28] },
VersionMetric { format: Format::Full, version_str: "28", layout_id: 2, rows: 129, cols: 129, fimax: 26, fimas: 50, datacap_bits: 15371, eclen: [390, 728, 1050, 1260], blocks: [3, 10, 3, 23, 4, 31, 11, 31] },
VersionMetric { format: Format::Full, version_str: "29", layout_id: 2, rows: 133, cols: 133, fimax: 30, fimas: 54, datacap_bits: 16411, eclen: [420, 784, 1140, 1350], blocks: [7, 7, 21, 7, 1, 37, 19, 26] },
VersionMetric { format: Format::Full, version_str: "30", layout_id: 2, rows: 137, cols: 137, fimax: 26, fimas: 52, datacap_bits: 17483, eclen: [450, 812, 1200, 1440], blocks: [5, 10, 19, 10, 15, 25, 23, 25] },
VersionMetric { format: Format::Full, version_str: "31", layout_id: 2, rows: 141, cols: 141, fimax: 30, fimas: 56, datacap_bits: 18587, eclen: [480, 868, 1290, 1530], blocks: [13, 3, 2, 29, 42, 1, 23, 28] },
VersionMetric { format: Format::Full, version_str: "32", layout_id: 2, rows: 145, cols: 145, fimax: 34, fimas: 60, datacap_bits: 19723, eclen: [510, 924, 1350, 1620], blocks: [17, 0, 10, 23, 10, 35, 19, 35] },
VersionMetric { format: Format::Full, version_str: "33", layout_id: 2, rows: 149, cols: 149, fimax: 30, fimas: 58, datacap_bits: 20891, eclen: [540, 980, 1440, 1710], blocks: [17, 1, 14, 21, 29, 19, 11, 46] },
VersionMetric { format: Format::Full, version_str: "34", layout_id: 2, rows: 153, cols: 153, fimax: 34, fimas: 62, datacap_bits: 22091, eclen: [570, 1036, 1530, 1800], blocks: [13, 6, 14, 23, 44, 7, 59, 1] },
VersionMetric { format: Format::Full, version_str: "35", layout_id: 2, rows: 157, cols: 157, fimax: 30, fimas: 54, datacap_bits: 23008, eclen: [570, 1064, 1590, 1890], blocks: [12, 7, 12, 26, 39, 14, 22, 41] },
VersionMetric { format: Format::Full, version_str: "36", layout_id: 2, rows: 161, cols: 161, fimax: 24, fimas: 50, datacap_bits: 24272, eclen: [600, 1120, 1680, 1980], blocks: [6, 14, 6, 34, 46, 10, 2, 64] },
VersionMetric { format: Format::Full, version_str: "37", layout_id: 2, rows: 165, cols: 165, fimax: 28, fimas: 54, datacap_bits: 25568, eclen: [630, 1204, 1770, 2100], blocks: [17, 4, 29, 14, 49, 10, 24, 46] },
VersionMetric { format: Format::Full, version_str: "38", layout_id: 2, rows: 169, cols: 169, fimax: 32, fimas: 58, datacap_bits: 26896, eclen: [660, 1260, 1860, 2220], blocks: [4, 18, 13, 32, 48, 14, 42, 32] },
VersionMetric { format: Format::Full, version_str: "39", layout_id: 2, rows: 173, cols: 173, fimax: 26, fimas: 54, datacap_bits: 28256, eclen: [720, 1316, 1950, 2310], blocks: [20, 4, 40, 7, 43, 22, 10, 67] },
VersionMetric { format: Format::Full, version_str: "40", layout_id: 2, rows: 177, cols: 177, fimax: 30, fimas: 58, datacap_bits: 29648, eclen: [750, 1372, 2040, 2430], blocks: [19, 6, 18, 31, 34, 34, 20, 61] },
VersionMetric { format: Format::Rmqr, version_str: "R7x43", layout_id: 7, rows: 7, cols: 43, fimax: 22, fimas: 99, datacap_bits: 104, eclen: [99, 7, 99, 10], blocks: [-1, -1, 1, 0, -1, -1, 1, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R7x59", layout_id: 8, rows: 7, cols: 59, fimax: 20, fimas: 40, datacap_bits: 171, eclen: [99, 9, 99, 14], blocks: [-1, -1, 1, 0, -1, -1, 1, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R7x77", layout_id: 9, rows: 7, cols: 77, fimax: 26, fimas: 52, datacap_bits: 261, eclen: [99, 12, 99, 22], blocks: [-1, -1, 1, 0, -1, -1, 1, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R7x99", layout_id: 10, rows: 7, cols: 99, fimax: 24, fimas: 50, datacap_bits: 358, eclen: [99, 16, 99, 30], blocks: [-1, -1, 1, 0, -1, -1, 1, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R7x139", layout_id: 11, rows: 7, cols: 139, fimax: 28, fimas: 56, datacap_bits: 545, eclen: [99, 24, 99, 44], blocks: [-1, -1, 1, 0, -1, -1, 2, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R9x43", layout_id: 12, rows: 9, cols: 43, fimax: 22, fimas: 99, datacap_bits: 170, eclen: [99, 9, 99, 14], blocks: [-1, -1, 1, 0, -1, -1, 1, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R9x59", layout_id: 13, rows: 9, cols: 59, fimax: 20, fimas: 40, datacap_bits: 267, eclen: [99, 12, 99, 22], blocks: [-1, -1, 1, 0, -1, -1, 1, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R9x77", layout_id: 14, rows: 9, cols: 77, fimax: 26, fimas: 52, datacap_bits: 393, eclen: [99, 18, 99, 32], blocks: [-1, -1, 1, 0, -1, -1, 1, 1] },
VersionMetric { format: Format::Rmqr, version_str: "R9x99", layout_id: 15, rows: 9, cols: 99, fimax: 24, fimas: 50, datacap_bits: 532, eclen: [99, 24, 99, 44], blocks: [-1, -1, 1, 0, -1, -1, 2, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R9x139", layout_id: 16, rows: 9, cols: 139, fimax: 28, fimas: 56, datacap_bits: 797, eclen: [99, 36, 99, 66], blocks: [-1, -1, 1, 1, -1, -1, 3, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R11x27", layout_id: 17, rows: 11, cols: 27, fimax: 98, fimas: 99, datacap_bits: 122, eclen: [99, 8, 99, 10], blocks: [-1, -1, 1, 0, -1, -1, 1, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R11x43", layout_id: 18, rows: 11, cols: 43, fimax: 22, fimas: 99, datacap_bits: 249, eclen: [99, 12, 99, 20], blocks: [-1, -1, 1, 0, -1, -1, 1, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R11x59", layout_id: 19, rows: 11, cols: 59, fimax: 20, fimas: 40, datacap_bits: 376, eclen: [99, 16, 99, 32], blocks: [-1, -1, 1, 0, -1, -1, 1, 1] },
VersionMetric { format: Format::Rmqr, version_str: "R11x77", layout_id: 20, rows: 11, cols: 77, fimax: 26, fimas: 52, datacap_bits: 538, eclen: [99, 24, 99, 44], blocks: [-1, -1, 1, 0, -1, -1, 1, 1] },
VersionMetric { format: Format::Rmqr, version_str: "R11x99", layout_id: 21, rows: 11, cols: 99, fimax: 24, fimas: 50, datacap_bits: 719, eclen: [99, 32, 99, 60], blocks: [-1, -1, 1, 1, -1, -1, 1, 1] },
VersionMetric { format: Format::Rmqr, version_str: "R11x139", layout_id: 22, rows: 11, cols: 139, fimax: 28, fimas: 56, datacap_bits: 1062, eclen: [99, 48, 99, 90], blocks: [-1, -1, 2, 0, -1, -1, 3, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R13x27", layout_id: 23, rows: 13, cols: 27, fimax: 98, fimas: 99, datacap_bits: 172, eclen: [99, 9, 99, 14], blocks: [-1, -1, 1, 0, -1, -1, 1, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R13x43", layout_id: 24, rows: 13, cols: 43, fimax: 22, fimas: 99, datacap_bits: 329, eclen: [99, 14, 99, 28], blocks: [-1, -1, 1, 0, -1, -1, 1, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R13x59", layout_id: 25, rows: 13, cols: 59, fimax: 20, fimas: 40, datacap_bits: 486, eclen: [99, 22, 99, 40], blocks: [-1, -1, 1, 0, -1, -1, 2, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R13x77", layout_id: 26, rows: 13, cols: 77, fimax: 26, fimas: 52, datacap_bits: 684, eclen: [99, 32, 99, 56], blocks: [-1, -1, 1, 1, -1, -1, 1, 1] },
VersionMetric { format: Format::Rmqr, version_str: "R13x99", layout_id: 27, rows: 13, cols: 99, fimax: 24, fimas: 50, datacap_bits: 907, eclen: [99, 40, 99, 78], blocks: [-1, -1, 1, 1, -1, -1, 1, 2] },
VersionMetric { format: Format::Rmqr, version_str: "R13x139", layout_id: 28, rows: 13, cols: 139, fimax: 28, fimas: 56, datacap_bits: 1328, eclen: [99, 60, 99, 112], blocks: [-1, -1, 2, 1, -1, -1, 2, 2] },
VersionMetric { format: Format::Rmqr, version_str: "R15x43", layout_id: 29, rows: 15, cols: 43, fimax: 22, fimas: 99, datacap_bits: 409, eclen: [99, 18, 99, 36], blocks: [-1, -1, 1, 0, -1, -1, 1, 1] },
VersionMetric { format: Format::Rmqr, version_str: "R15x59", layout_id: 30, rows: 15, cols: 59, fimax: 20, fimas: 40, datacap_bits: 596, eclen: [99, 26, 99, 48], blocks: [-1, -1, 1, 0, -1, -1, 2, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R15x77", layout_id: 31, rows: 15, cols: 77, fimax: 26, fimas: 52, datacap_bits: 830, eclen: [99, 36, 99, 72], blocks: [-1, -1, 1, 1, -1, -1, 2, 1] },
VersionMetric { format: Format::Rmqr, version_str: "R15x99", layout_id: 32, rows: 15, cols: 99, fimax: 24, fimas: 50, datacap_bits: 1095, eclen: [99, 48, 99, 88], blocks: [-1, -1, 2, 0, -1, -1, 4, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R15x139", layout_id: 33, rows: 15, cols: 139, fimax: 28, fimas: 56, datacap_bits: 1594, eclen: [99, 72, 99, 130], blocks: [-1, -1, 2, 1, -1, -1, 1, 4] },
VersionMetric { format: Format::Rmqr, version_str: "R17x43", layout_id: 34, rows: 17, cols: 43, fimax: 22, fimas: 99, datacap_bits: 489, eclen: [99, 22, 99, 40], blocks: [-1, -1, 1, 0, -1, -1, 1, 1] },
VersionMetric { format: Format::Rmqr, version_str: "R17x59", layout_id: 35, rows: 17, cols: 59, fimax: 20, fimas: 40, datacap_bits: 706, eclen: [99, 32, 99, 60], blocks: [-1, -1, 2, 0, -1, -1, 2, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R17x77", layout_id: 36, rows: 17, cols: 77, fimax: 26, fimas: 52, datacap_bits: 976, eclen: [99, 44, 99, 84], blocks: [-1, -1, 2, 0, -1, -1, 1, 2] },
VersionMetric { format: Format::Rmqr, version_str: "R17x99", layout_id: 37, rows: 17, cols: 99, fimax: 24, fimas: 50, datacap_bits: 1283, eclen: [99, 60, 99, 104], blocks: [-1, -1, 2, 1, -1, -1, 4, 0] },
VersionMetric { format: Format::Rmqr, version_str: "R17x139", layout_id: 38, rows: 17, cols: 139, fimax: 28, fimas: 56, datacap_bits: 1860, eclen: [99, 80, 99, 156], blocks: [-1, -1, 4, 0, -1, -1, 2, 4] },
];
pub(crate) const BCH_15_5_POLY: u32 = 0x537;
pub(crate) const FORMAT_INFO_MASK_FULL: u16 = 0x5412;
pub(crate) const FORMAT_INFO_MASK_MICRO: u16 = 0x4445;
pub(crate) const BCH_18_6_POLY: u32 = 0x1F25;
pub(crate) fn bch15_5_encode(data: u8) -> u16 {
debug_assert!(data < 32, "format-info data must be 5 bits");
let mut d: u32 = u32::from(data) << 10;
for shift in (10..=14).rev() {
if d & (1u32 << shift) != 0 {
d ^= BCH_15_5_POLY << (shift - 10);
}
}
let ecc = d & 0x3FF;
((u32::from(data) << 10) | ecc) as u16
}
pub(crate) fn bch18_6_encode(version: u8) -> u32 {
debug_assert!(
(7..=40).contains(&version),
"version-info only encoded for V7+"
);
bch18_6_encode_data(version)
}
pub(crate) fn bch18_6_encode_data(data: u8) -> u32 {
debug_assert!(data < 64, "BCH(18,6) input must be 6 bits (0..=63)");
let mut d: u32 = u32::from(data) << 12;
for shift in (12..=17).rev() {
if d & (1u32 << shift) != 0 {
d ^= BCH_18_6_POLY << (shift - 12);
}
}
let ecc = d & 0xFFF;
(u32::from(data) << 12) | ecc
}
pub(crate) const RMQR_FMTVAL1_MASK: u32 = 0x1_FAB2;
pub(crate) const RMQR_FMTVAL2_MASK: u32 = 0x2_0A7B;
pub(crate) fn rmqr_fmtval1(data: u8) -> u32 {
bch18_6_encode_data(data) ^ RMQR_FMTVAL1_MASK
}
pub(crate) fn rmqr_fmtval2(data: u8) -> u32 {
bch18_6_encode_data(data) ^ RMQR_FMTVAL2_MASK
}
pub(crate) const QRCODE_EC_INDICATOR_RMQR: [i8; 4] = [-1, 0, -1, 1];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Mode {
Numeric = 0,
Alphanumeric = 1,
Byte = 2,
Kanji = 3,
Eci = 4,
}
pub(crate) const ALPHANUMERIC_VALUE: [i8; 256] = {
let mut t = [-1i8; 256];
let mut i = 0;
while i < 10 {
t[(b'0' + i as u8) as usize] = i as i8;
i += 1;
}
let mut j = 0;
while j < 26 {
t[(b'A' + j as u8) as usize] = (10 + j) as i8;
j += 1;
}
t[b' ' as usize] = 36;
t[b'$' as usize] = 37;
t[b'%' as usize] = 38;
t[b'*' as usize] = 39;
t[b'+' as usize] = 40;
t[b'-' as usize] = 41;
t[b'.' as usize] = 42;
t[b'/' as usize] = 43;
t[b':' as usize] = 44;
t
};
pub(crate) fn push_bits(out: &mut Vec<bool>, value: u32, nbits: u8) {
for shift in (0..nbits).rev() {
out.push(((value >> shift) & 1) == 1);
}
}
pub(crate) fn encode_numeric_segment(digits: &[u8]) -> Result<Vec<bool>, Error> {
let mut out: Vec<bool> = Vec::with_capacity((digits.len() * 10).div_ceil(3));
let mut i = 0;
while i < digits.len() {
let remaining = digits.len() - i;
match remaining {
r if r >= 3 => {
let d0 = digit_value(digits[i])?;
let d1 = digit_value(digits[i + 1])?;
let d2 = digit_value(digits[i + 2])?;
let v = u32::from(d0) * 100 + u32::from(d1) * 10 + u32::from(d2);
push_bits(&mut out, v, 10);
i += 3;
}
2 => {
let d0 = digit_value(digits[i])?;
let d1 = digit_value(digits[i + 1])?;
push_bits(&mut out, u32::from(d0) * 10 + u32::from(d1), 7);
i += 2;
}
_ => {
let d0 = digit_value(digits[i])?;
push_bits(&mut out, u32::from(d0), 4);
i += 1;
}
}
}
Ok(out)
}
#[inline]
fn digit_value(b: u8) -> Result<u8, Error> {
if b.is_ascii_digit() {
Ok(b - b'0')
} else {
Err(Error::InvalidData(format!(
"qrcode_native: numeric mode expects ASCII digit, got 0x{b:02X}"
)))
}
}
pub(crate) fn encode_alphanumeric_segment(input: &[u8]) -> Result<Vec<bool>, Error> {
let mut out: Vec<bool> = Vec::with_capacity(input.len() * 11 / 2 + 1);
let mut i = 0;
while i < input.len() {
let remaining = input.len() - i;
if remaining >= 2 {
let c0 = alpha_value(input[i])?;
let c1 = alpha_value(input[i + 1])?;
push_bits(&mut out, u32::from(c0) * 45 + u32::from(c1), 11);
i += 2;
} else {
let c0 = alpha_value(input[i])?;
push_bits(&mut out, u32::from(c0), 6);
i += 1;
}
}
Ok(out)
}
#[inline]
fn alpha_value(b: u8) -> Result<u8, Error> {
let v = ALPHANUMERIC_VALUE[b as usize];
if v < 0 {
Err(Error::InvalidData(format!(
"qrcode_native: alphanumeric mode rejects byte 0x{b:02X}"
)))
} else {
Ok(v as u8)
}
}
pub(crate) fn encode_byte_segment(bytes: &[u8]) -> Vec<bool> {
let mut out: Vec<bool> = Vec::with_capacity(bytes.len() * 8);
for &b in bytes {
push_bits(&mut out, u32::from(b), 8);
}
out
}
pub(crate) fn encode_kanji_segment(jis: &[u16]) -> Result<Vec<bool>, Error> {
let mut out: Vec<bool> = Vec::with_capacity(jis.len() * 13);
for &c in jis {
let offset: u32 = if (0x8140..=0x9FFC).contains(&c) {
u32::from(c) - 0x8140
} else if (0xE040..=0xEBBF).contains(&c) {
u32::from(c) - 0xC140
} else {
return Err(Error::InvalidData(format!(
"qrcode_native: kanji mode rejects Shift-JIS 0x{c:04X}"
)));
};
let hi = offset >> 8;
let lo = offset & 0xFF;
push_bits(&mut out, hi * 0xC0 + lo, 13);
}
Ok(out)
}
pub(crate) fn encode_eci_segment(eci: u32) -> Result<Vec<bool>, Error> {
let mut out = Vec::with_capacity(24);
if eci <= 127 {
push_bits(&mut out, eci, 8);
} else if eci <= 16_383 {
push_bits(&mut out, eci | 0x8000, 16);
} else if eci <= 999_999 {
push_bits(&mut out, eci | 0xC0_0000, 24);
} else {
return Err(Error::InvalidData(format!(
"qrcode_native: ECI assignment number {eci} exceeds 6-digit max"
)));
}
Ok(out)
}
pub(crate) const CC_LENS: [[i8; 4]; 39] = [
[10, 9, 8, 8], [12, 11, 16, 10], [14, 13, 16, 12], [3, -1, -1, -1], [4, 3, -1, -1], [5, 4, 4, 3], [6, 5, 5, 4], [4, 3, 3, 2], [5, 5, 4, 3], [6, 5, 5, 4], [7, 6, 5, 5], [7, 6, 6, 5], [5, 5, 4, 3], [6, 5, 5, 4], [7, 6, 5, 5], [7, 6, 6, 5], [8, 7, 6, 6], [4, 4, 3, 2], [6, 5, 5, 4], [7, 6, 5, 5], [7, 6, 6, 5], [8, 7, 6, 6], [8, 7, 7, 6], [5, 5, 4, 3], [6, 6, 5, 5], [7, 6, 6, 5], [7, 7, 6, 6], [8, 7, 7, 6], [8, 8, 7, 7], [7, 6, 6, 5], [7, 7, 6, 5], [8, 7, 7, 6], [8, 7, 7, 6], [9, 8, 7, 7], [7, 6, 6, 5], [8, 7, 6, 6], [8, 7, 7, 6], [8, 8, 7, 6], [9, 8, 8, 7], ];
pub(crate) fn cci_bits(layout_id: u8, mode: Mode) -> Option<u8> {
if matches!(mode, Mode::Eci) {
return None;
}
let bin = layout_id as usize;
if bin >= CC_LENS.len() {
return None;
}
let col = mode as usize;
let v = CC_LENS[bin][col];
if v < 0 {
None
} else {
Some(v as u8)
}
}
pub(crate) const INFEAS: u16 = 10_000;
pub(crate) const QRCODE_MIDS: [[Option<&str>; 5]; 39] = {
let n = None;
[
[
Some("0001"),
Some("0010"),
Some("0100"),
Some("1000"),
Some("0111"),
],
[
Some("0001"),
Some("0010"),
Some("0100"),
Some("1000"),
Some("0111"),
],
[
Some("0001"),
Some("0010"),
Some("0100"),
Some("1000"),
Some("0111"),
],
[Some(""), n, n, n, n],
[Some("0"), Some("1"), n, n, n],
[Some("00"), Some("01"), Some("10"), Some("11"), n],
[Some("000"), Some("001"), Some("010"), Some("011"), n],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
[
Some("001"),
Some("010"),
Some("011"),
Some("100"),
Some("111"),
],
]
};
pub(crate) const QRCODE_MODE0_FORCE_KB: [u16; 39] = [
1, 1, 1, INFEAS, INFEAS, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
];
pub(crate) const QRCODE_MODE0_FORCE_A: [u16; 39] = [
1, 1, 1, INFEAS, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1,
];
pub(crate) const QRCODE_MODE0_FORCE_N: [u16; 39] = [1u16; 39];
pub(crate) const QRCODE_MODE0_N_BEFORE_B: [u16; 39] = [
4, 4, 5, INFEAS, INFEAS, 2, 3, 2, 2, 3, 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
];
pub(crate) const QRCODE_MODE_BK_BEFORE_B: [u16; 39] = [
9, 12, 13, INFEAS, INFEAS, 4, 6, 4, 5, 6, 6, 6, 5, 6, 6, 6, 7, 4, 6, 6, 6, 7, 7, 5, 6, 6, 7, 7,
7, 6, 6, 7, 7, 7, 6, 7, 7, 7, 8,
];
pub(crate) const QRCODE_MODE_BK_BEFORE_A: [u16; 39] = [
8, 10, 11, INFEAS, INFEAS, 4, 5, 4, 5, 5, 6, 6, 5, 5, 6, 6, 6, 4, 5, 6, 6, 6, 6, 5, 6, 6, 6, 6,
7, 6, 6, 6, 6, 7, 6, 6, 6, 7, 7,
];
pub(crate) const QRCODE_MODE_BK_BEFORE_N: [u16; 39] = [
8, 9, 11, INFEAS, INFEAS, 3, 5, 3, 4, 5, 5, 5, 4, 5, 5, 5, 6, 3, 5, 5, 5, 6, 6, 4, 5, 5, 6, 6,
6, 5, 5, 6, 6, 7, 5, 6, 6, 6, 7,
];
pub(crate) const QRCODE_MODE_BK_BEFORE_E: [u16; 39] = [
5, 5, 6, INFEAS, INFEAS, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 2, 3, 3, 3, 4, 4, 3, 3, 3, 4, 4,
4, 3, 3, 4, 4, 4, 3, 4, 4, 4, 4,
];
pub(crate) const QRCODE_MODE_BA_BEFORE_K: [u16; 39] = [
11, 12, 14, INFEAS, INFEAS, 5, 7, 5, 6, 7, 8, 8, 6, 7, 8, 8, 8, 6, 7, 8, 8, 8, 8, 6, 8, 8, 8,
8, 9, 8, 8, 8, 8, 9, 8, 8, 8, 9, 9,
];
pub(crate) const QRCODE_MODE_BA_BEFORE_B: [u16; 39] = [
11, 15, 16, INFEAS, INFEAS, 6, 7, 6, 7, 7, 8, 8, 7, 7, 8, 8, 8, 6, 7, 8, 8, 8, 9, 7, 8, 8, 8,
9, 9, 8, 8, 9, 9, 9, 8, 8, 9, 9, 10,
];
pub(crate) const QRCODE_MODE_BA_BEFORE_N: [u16; 39] = [
12, 13, 15, INFEAS, INFEAS, 6, 8, 6, 7, 8, 8, 8, 7, 8, 8, 8, 9, 6, 8, 8, 8, 9, 9, 7, 8, 8, 9,
9, 10, 8, 9, 9, 9, 10, 8, 9, 9, 10, 10,
];
pub(crate) const QRCODE_MODE_BA_BEFORE_E: [u16; 39] = [
6, 7, 8, INFEAS, INFEAS, 3, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 4, 4, 4, 4, 5, 5, 4, 4, 4, 5, 5,
5, 4, 5, 5, 5, 5, 4, 5, 5, 5, 5,
];
pub(crate) const QRCODE_MODE_BN_BEFORE_K: [u16; 39] = [
6, 7, 8, INFEAS, INFEAS, 3, 4, 3, 4, 4, 5, 5, 4, 4, 5, 5, 5, 3, 4, 5, 5, 5, 5, 4, 4, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
];
pub(crate) const QRCODE_MODE_BN_BEFORE_B: [u16; 39] = [
6, 8, 9, INFEAS, INFEAS, 3, 4, 3, 4, 4, 5, 5, 4, 4, 5, 5, 5, 3, 4, 5, 5, 5, 5, 4, 4, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
];
pub(crate) const QRCODE_MODE_BN_BEFORE_A: [u16; 39] = [
6, 7, 8, INFEAS, INFEAS, 3, 4, 3, 4, 4, 5, 5, 4, 4, 5, 5, 5, 4, 4, 5, 5, 5, 5, 4, 5, 5, 5, 5,
5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6,
];
pub(crate) const QRCODE_MODE_BN_BEFORE_E: [u16; 39] = [
3, 4, 4, INFEAS, INFEAS, 2, 3, 2, 2, 3, 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
];
pub(crate) const QRCODE_MODE_AN_BEFORE_A: [u16; 39] = [
13, 15, 17, INFEAS, 5, 7, 9, 7, 8, 9, 9, 9, 8, 9, 9, 9, 11, 7, 9, 9, 9, 11, 11, 8, 9, 9, 10,
11, 11, 9, 10, 11, 11, 11, 9, 11, 11, 11, 11,
];
pub(crate) const QRCODE_MODE_AN_BEFORE_B: [u16; 39] = [
13, 17, 18, INFEAS, INFEAS, 7, 9, 7, 8, 9, 9, 9, 8, 9, 9, 9, 10, 7, 9, 9, 9, 10, 11, 8, 9, 9,
9, 11, 11, 9, 9, 11, 11, 11, 9, 10, 11, 11, 11,
];
pub(crate) const QRCODE_MODE_AN_BEFORE_E: [u16; 39] = [
7, 8, 9, INFEAS, 3, 4, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 4, 5, 5, 5, 6, 6, 5, 5, 5, 5, 6, 6, 5,
5, 6, 6, 6, 5, 6, 6, 6, 6,
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct InputCounters {
pub num_n: Vec<u32>,
pub num_a: Vec<u32>,
pub num_a_or_n: Vec<u32>,
pub num_b: Vec<u32>,
pub num_k: Vec<u32>,
pub next_n: Vec<u32>,
pub next_a: Vec<u32>,
pub next_b: Vec<u32>,
pub next_k: Vec<u32>,
pub is_eci: Vec<bool>,
}
#[inline]
pub(crate) fn is_kanji_leader(b: u8) -> bool {
(0x81..=0x9F).contains(&b) || (0xE0..=0xEB).contains(&b)
}
#[inline]
fn is_valid_shift_jis(hi: u8, lo: u8) -> bool {
let val = ((hi as u16) << 8) | (lo as u16);
let in_range = (0x8140..=0x9FFC).contains(&val) || (0xE040..=0xEBBF).contains(&val);
let low_ok = (0x40..=0xFC).contains(&lo) && lo != 0x7F;
in_range && low_ok
}
pub(crate) fn compute_input_counters(msg: &[u8], suppress_kanji_mode: bool) -> InputCounters {
let n = msg.len();
let mut num_n = vec![0u32; n + 1];
let mut num_a = vec![0u32; n + 1];
let mut num_a_or_n = vec![0u32; n + 1];
let mut num_k = vec![0u32; n + 1];
let mut next_n = vec![0u32; n + 1];
let mut next_a = vec![0u32; n + 1];
let mut next_k = vec![0u32; n + 1];
let is_eci = vec![false; n + 1];
if n > 0 {
next_k[n] = 9999;
}
for i in (0..n).rev() {
let b = msg[i];
let is_k = !suppress_kanji_mode
&& is_kanji_leader(b)
&& i + 1 < n
&& is_valid_shift_jis(b, msg[i + 1]);
if is_k {
next_k[i] = 0;
num_k[i] = num_k[i + 2] + 1;
} else {
next_k[i] = next_k[i + 1] + 1;
}
let is_digit = b.is_ascii_digit();
if is_digit {
next_n[i] = 0;
num_n[i] = num_n[i + 1] + 1;
num_a_or_n[i] = num_a_or_n[i + 1] + 1;
} else {
next_n[i] = next_n[i + 1] + 1;
}
let is_alpha_only = is_alpha_only_byte(b);
if is_alpha_only {
next_a[i] = 0;
num_a[i] = num_a[i + 1] + 1;
num_a_or_n[i] = num_a_or_n[i + 1] + 1;
} else {
next_a[i] = next_a[i + 1] + 1;
}
}
for i in 0..n.saturating_sub(1) {
if num_k[i] > 0 {
num_k[i + 1] = 0;
next_k[i + 1] = next_k[i + 1].saturating_add(1);
}
}
let mut num_b = vec![0u32; n + 1];
let mut next_b = vec![0u32; n + 1];
for i in (0..n).rev() {
let is_byte = num_n[i] + num_a[i] + num_k[i] == 0 && !is_eci[i];
if is_byte {
next_b[i] = 0;
num_b[i] = num_b[i + 1] + 1;
} else {
next_b[i] = next_b[i + 1] + 1;
}
}
InputCounters {
num_n,
num_a,
num_a_or_n,
num_b,
num_k,
next_n,
next_a,
next_b,
next_k,
is_eci,
}
}
#[inline]
fn is_alpha_only_byte(b: u8) -> bool {
matches!(
b,
b'A'..=b'Z' | b' ' | b'$' | b'%' | b'*' | b'+' | b'-' | b'.' | b'/' | b':'
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Segment {
pub mode: Mode,
pub start: usize,
pub len: usize,
}
pub(crate) fn select_segments(msg: &[u8], layout_id: u8, fnc1first: bool) -> Vec<Segment> {
let n = msg.len();
if n == 0 {
return Vec::new();
}
let counters = compute_input_counters(msg, true);
let bin = layout_id as usize;
let mut segments: Vec<Segment> = Vec::new();
let mut mode: Option<Mode> = None;
let mut start: usize = 0;
let mut i: usize = 0;
while i < n {
let num_k = counters.num_k[i];
let num_b = counters.num_b[i];
let num_a = counters.num_a[i];
let num_n = counters.num_n[i];
let num_a_or_n = counters.num_a_or_n[i];
let k_before_b = |table: &[u16; 39]| {
num_k >= table[bin] as u32 && counters.next_b[i + 2 * num_k as usize] == 0
};
let k_before_a = |table: &[u16; 39]| {
num_k >= table[bin] as u32 && counters.next_a[i + 2 * num_k as usize] == 0
};
let k_before_n = |table: &[u16; 39]| {
num_k >= table[bin] as u32 && counters.next_n[i + 2 * num_k as usize] == 0
};
let k_before_e =
|table: &[u16; 39]| num_k >= table[bin] as u32 && (i + 2 * num_k as usize) == n;
let a_before_k = |table: &[u16; 39]| {
num_a >= table[bin] as u32 && counters.next_k[i + num_a as usize] == 0
};
let a_before_b = |table: &[u16; 39]| {
num_a >= table[bin] as u32 && counters.next_b[i + num_a as usize] == 0
};
let a_before_n = |table: &[u16; 39]| {
num_a >= table[bin] as u32 && counters.next_n[i + num_a as usize] == 0
};
let a_before_e =
|table: &[u16; 39]| num_a >= table[bin] as u32 && (i + num_a as usize) == n;
let n_before_k = |table: &[u16; 39]| {
num_n >= table[bin] as u32 && counters.next_k[i + num_n as usize] == 0
};
let n_before_b = |table: &[u16; 39]| {
num_n >= table[bin] as u32 && counters.next_b[i + num_n as usize] == 0
};
let n_before_a = |table: &[u16; 39]| {
num_n >= table[bin] as u32 && counters.next_a[i + num_n as usize] == 0
};
let n_before_e =
|table: &[u16; 39]| num_n >= table[bin] as u32 && (i + num_n as usize) == n;
let a_or_n_before_b = |table: &[u16; 39]| {
num_a_or_n >= table[bin] as u32 && counters.next_b[i + num_a_or_n as usize] == 0
};
let a_or_n_before_e =
|table: &[u16; 39]| num_a_or_n >= table[bin] as u32 && (i + num_a_or_n as usize) == n;
#[allow(clippy::never_loop)]
let next_mode: Mode = 'pick: loop {
if mode.is_none() {
if k_before_a(&QRCODE_MODE0_FORCE_KB) {
break 'pick Mode::Kanji;
}
if k_before_n(&QRCODE_MODE0_FORCE_KB) {
break 'pick Mode::Kanji;
}
if k_before_b(&QRCODE_MODE_BK_BEFORE_E) {
break 'pick Mode::Kanji;
}
if k_before_e(&QRCODE_MODE0_FORCE_KB) {
break 'pick Mode::Kanji;
}
if num_k >= 1 {
break 'pick Mode::Byte;
}
if n_before_k(&QRCODE_MODE0_N_BEFORE_B) {
break 'pick Mode::Numeric;
}
if n_before_b(&QRCODE_MODE0_N_BEFORE_B) {
break 'pick Mode::Numeric;
}
if n_before_b(&QRCODE_MODE0_FORCE_KB) {
break 'pick Mode::Byte;
}
if n_before_a(&QRCODE_MODE_AN_BEFORE_E) {
break 'pick Mode::Numeric;
}
if n_before_e(&QRCODE_MODE0_FORCE_N) {
break 'pick Mode::Numeric;
}
if a_before_k(&QRCODE_MODE_BA_BEFORE_E) {
break 'pick Mode::Alphanumeric;
}
if a_or_n_before_b(&QRCODE_MODE_BA_BEFORE_E) {
break 'pick Mode::Alphanumeric;
}
if a_or_n_before_e(&QRCODE_MODE0_FORCE_A) {
break 'pick Mode::Alphanumeric;
}
break 'pick Mode::Byte;
}
let m = mode.unwrap();
if matches!(m, Mode::Byte) {
if k_before_b(&QRCODE_MODE_BK_BEFORE_B) {
break 'pick Mode::Kanji;
}
if k_before_a(&QRCODE_MODE_BK_BEFORE_A) {
break 'pick Mode::Kanji;
}
if k_before_n(&QRCODE_MODE_BK_BEFORE_N) {
break 'pick Mode::Kanji;
}
if k_before_e(&QRCODE_MODE_BK_BEFORE_E) {
break 'pick Mode::Kanji;
}
if a_before_k(&QRCODE_MODE_BA_BEFORE_K) {
break 'pick Mode::Alphanumeric;
}
if a_before_b(&QRCODE_MODE_BA_BEFORE_B) {
break 'pick Mode::Alphanumeric;
}
if a_before_n(&QRCODE_MODE_BA_BEFORE_N) {
break 'pick Mode::Alphanumeric;
}
if a_before_e(&QRCODE_MODE_BA_BEFORE_E) {
break 'pick Mode::Alphanumeric;
}
if n_before_k(&QRCODE_MODE_BN_BEFORE_K) {
break 'pick Mode::Numeric;
}
if n_before_b(&QRCODE_MODE_BN_BEFORE_B) {
break 'pick Mode::Numeric;
}
if n_before_a(&QRCODE_MODE_BN_BEFORE_A) {
break 'pick Mode::Numeric;
}
if n_before_e(&QRCODE_MODE_BN_BEFORE_E) {
break 'pick Mode::Numeric;
}
break 'pick Mode::Byte;
}
if matches!(m, Mode::Alphanumeric) {
if num_k >= 1 {
break 'pick Mode::Kanji;
}
if num_b >= 1 {
break 'pick Mode::Byte;
}
if n_before_a(&QRCODE_MODE_AN_BEFORE_A) {
break 'pick Mode::Numeric;
}
if n_before_b(&QRCODE_MODE_AN_BEFORE_B) {
break 'pick Mode::Numeric;
}
if n_before_e(&QRCODE_MODE_AN_BEFORE_E) {
break 'pick Mode::Numeric;
}
if num_a >= 1 || num_n >= 1 {
break 'pick Mode::Alphanumeric;
}
break 'pick Mode::Byte;
}
if matches!(m, Mode::Numeric) {
if num_k >= 1 {
break 'pick Mode::Kanji;
}
if num_b >= 1 {
break 'pick Mode::Byte;
}
if num_a >= 1 {
break 'pick Mode::Alphanumeric;
}
if num_n >= 1 {
break 'pick Mode::Numeric;
}
break 'pick Mode::Byte;
}
if matches!(m, Mode::Kanji) {
if num_b >= 1 {
break 'pick Mode::Byte;
}
if num_a >= 1 {
break 'pick Mode::Alphanumeric;
}
if num_n >= 1 {
break 'pick Mode::Numeric;
}
if num_k >= 1 {
break 'pick Mode::Kanji;
}
break 'pick Mode::Byte;
}
break 'pick Mode::Byte;
};
let next_mode = if matches!(next_mode, Mode::Kanji) && fnc1first {
Mode::Byte
} else {
next_mode
};
let advance: usize = match next_mode {
Mode::Kanji => 2 * num_k as usize,
Mode::Byte => num_b as usize,
Mode::Alphanumeric => num_a as usize,
Mode::Numeric => num_n as usize,
Mode::Eci => 1,
};
let advance = if advance == 0 { 1 } else { advance };
let chosen = if advance == 0 { Mode::Byte } else { next_mode };
if let Some(last) = segments.last_mut() {
if last.mode == chosen && last.start + last.len == i {
last.len += advance;
i += advance;
mode = Some(chosen);
let _ = start; continue;
}
}
segments.push(Segment {
mode: chosen,
start: i,
len: advance,
});
i += advance;
mode = Some(chosen);
start = i;
}
segments
}
pub(crate) fn compose_segments(
msg: &[u8],
segments: &[Segment],
layout_id: u8,
fnc1first: bool,
) -> Result<Vec<bool>, Error> {
let mut bits: Vec<bool> = Vec::new();
let bin = layout_id as usize;
if bin >= QRCODE_MIDS.len() {
return Err(Error::InvalidData(format!(
"qrcode_native: layout_id {layout_id} out of range"
)));
}
if fnc1first {
let prefix = if layout_id < 7 { "0101" } else { "101" };
for c in prefix.chars() {
bits.push(c == '1');
}
}
for seg in segments {
let mode_idx = seg.mode as usize;
let mid = QRCODE_MIDS[bin][mode_idx].ok_or_else(|| {
Error::InvalidData(format!(
"qrcode_native: mode {:?} not supported by layout_id {layout_id}",
seg.mode
))
})?;
for c in mid.chars() {
bits.push(c == '1');
}
let char_count = match seg.mode {
Mode::Kanji => seg.len / 2,
_ => seg.len,
};
if !matches!(seg.mode, Mode::Eci) {
let cci_width = cci_bits(layout_id, seg.mode).ok_or_else(|| {
Error::InvalidData(format!(
"qrcode_native: no CCI width for layout_id={layout_id} mode={:?}",
seg.mode
))
})?;
push_bits(&mut bits, char_count as u32, cci_width);
}
let chunk = &msg[seg.start..seg.start + seg.len];
let payload: Vec<bool> = match seg.mode {
Mode::Numeric => encode_numeric_segment(chunk)?,
Mode::Alphanumeric => encode_alphanumeric_segment(chunk)?,
Mode::Byte => encode_byte_segment(chunk),
Mode::Kanji => {
let mut codes: Vec<u16> = Vec::with_capacity(chunk.len() / 2);
let mut p = 0;
while p + 1 < chunk.len() {
let code = ((chunk[p] as u16) << 8) | (chunk[p + 1] as u16);
codes.push(code);
p += 2;
}
encode_kanji_segment(&codes)?
}
Mode::Eci => {
return Err(Error::InvalidData(
"qrcode_native: ECI segments are outside this port's compose_segments scope (BWIPP's parsefnc-driven ECI escapes use a separate codepath; for plain Latin-1/UTF-8 payloads use Byte mode, which is selected automatically by the mode selector)".into(),
));
}
};
bits.extend_from_slice(&payload);
}
Ok(bits)
}
pub(crate) fn bits_to_bytes(bits: &[bool]) -> Vec<u8> {
let mut out = vec![0u8; bits.len().div_ceil(8)];
for (i, &b) in bits.iter().enumerate() {
if b {
out[i / 8] |= 1 << (7 - (i % 8));
}
}
out
}
pub(crate) const TERMINATOR_LEN: [u8; 39] = {
let mut t = [3u8; 39];
t[0] = 4;
t[1] = 4;
t[2] = 4;
t[3] = 3;
t[4] = 5;
t[5] = 7;
t[6] = 9;
t
};
pub(crate) const PADDING_CODEWORDS: [u8; 2] = [0xEC, 0x11];
pub(crate) fn pad_codewords(
bits: &[bool],
layout_id: u8,
data_capacity_bits: u32,
data_codeword_count: u32,
) -> Result<Vec<u8>, Error> {
if bits.len() as u32 > data_capacity_bits {
return Err(Error::InvalidData(format!(
"qrcode_native: bit-stream ({} bits) exceeds capacity ({data_capacity_bits} bits)",
bits.len()
)));
}
let term_len = TERMINATOR_LEN[layout_id as usize] as u32;
let remaining = data_capacity_bits - bits.len() as u32;
let actual_term = term_len.min(remaining);
let mut buf: Vec<bool> = Vec::with_capacity(data_codeword_count as usize * 8);
buf.extend_from_slice(bits);
buf.extend(std::iter::repeat_n(false, actual_term as usize));
let bytes_remainder = buf.len() % 8;
if bytes_remainder != 0 {
buf.extend(std::iter::repeat_n(false, 8 - bytes_remainder));
}
let mut bytes = bits_to_bytes(&buf);
let lc4b = layout_id == 3 || layout_id == 5;
let mut idx = 0;
if lc4b {
let target = data_codeword_count as usize - 1;
bytes.truncate(target);
while bytes.len() < target {
bytes.push(PADDING_CODEWORDS[idx]);
idx = (idx + 1) % 2;
}
let last_codeword_start = target * 8;
let mut high_nibble = 0u8;
for k in 0..4 {
if last_codeword_start + k < buf.len() && buf[last_codeword_start + k] {
high_nibble |= 1 << (3 - k);
}
}
bytes.push(high_nibble << 4);
} else {
while bytes.len() < data_codeword_count as usize {
bytes.push(PADDING_CODEWORDS[idx]);
idx = (idx + 1) % 2;
}
}
Ok(bytes)
}
pub(crate) const QR_GF256_EXP: [u8; 256] = {
let mut t = [0u8; 256];
let mut v: u16 = 1;
let mut i = 0;
while i < 255 {
t[i] = v as u8;
v <<= 1;
if v >= 256 {
v ^= 0x011D;
}
i += 1;
}
t
};
pub(crate) const QR_GF256_LOG: [u8; 256] = {
let mut t = [0u8; 256];
let mut i: usize = 1;
while i < 255 {
t[QR_GF256_EXP[i] as usize] = i as u8;
i += 1;
}
t
};
#[inline]
pub(crate) fn qr_gf256_mul(a: u8, b: u8) -> u8 {
if a == 0 || b == 0 {
return 0;
}
let la = QR_GF256_LOG[a as usize] as u16;
let lb = QR_GF256_LOG[b as usize] as u16;
QR_GF256_EXP[((la + lb) % 255) as usize]
}
pub(crate) fn qr_rs_gen_coeffs(ec_len: usize) -> Vec<u8> {
let mut coeffs = vec![0u8; ec_len + 1];
coeffs[0] = 1;
for i in 0..ec_len {
coeffs[i + 1] = coeffs[i];
let alpha_i = QR_GF256_EXP[i];
for j in (1..=i).rev() {
coeffs[j] = qr_gf256_mul(coeffs[j], alpha_i) ^ coeffs[j - 1];
}
coeffs[0] = qr_gf256_mul(coeffs[0], alpha_i);
}
coeffs.truncate(ec_len);
coeffs
}
pub(crate) fn qr_rs_block_ecc(data: &[u8], ec_len: usize) -> Vec<u8> {
let coeffs = qr_rs_gen_coeffs(ec_len);
let mut lfsr = vec![0u8; ec_len];
for &d in data {
let feedback = d ^ lfsr[ec_len - 1];
for k in (1..ec_len).rev() {
lfsr[k] = lfsr[k - 1] ^ qr_gf256_mul(coeffs[k], feedback);
}
lfsr[0] = qr_gf256_mul(coeffs[0], feedback);
}
lfsr.reverse();
lfsr
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct BlockLayout {
pub ncws: u32,
pub dcws: u32,
pub ecpb: u32,
pub ecb1: u32,
pub ecb2: u32,
pub dcpb: u32,
pub rbit: u8,
pub lc4b: bool,
}
pub(crate) fn block_layout(metric_idx: usize, ec_level: u8) -> Result<BlockLayout, Error> {
if ec_level >= 4 {
return Err(Error::InvalidData(format!(
"qrcode_native: ec_level {ec_level} out of range (0..=3 = L/M/Q/H)"
)));
}
if metric_idx >= FULL_METRICS.len() {
return Err(Error::InvalidData(format!(
"qrcode_native: metric_idx {metric_idx} out of range (0..{})",
FULL_METRICS.len()
)));
}
let m = &FULL_METRICS[metric_idx];
let lc4b = m.version_str == "M1" || m.version_str == "M3";
let mut ncws = m.datacap_bits / 8;
let mut rbit = (m.datacap_bits % 8) as u8;
if lc4b {
ncws += 1;
rbit = 0;
}
let ec_idx = ec_level as usize;
let ecws = m.eclen[ec_idx];
let ecb1 = m.blocks[ec_idx * 2];
let ecb2 = m.blocks[ec_idx * 2 + 1];
if ecws as i32 == NA_I8 as i32 || ecb1 < 0 || ecb2 < 0 {
return Err(Error::InvalidData(format!(
"qrcode_native: format/version `{}` does not support EC level {}",
m.version_str,
"LMQH".as_bytes()[ec_idx] as char
)));
}
let ecws = u32::from(ecws);
let dcws = ncws - ecws;
let ecb1 = ecb1 as u32;
let ecb2 = ecb2 as u32;
let num_blocks = ecb1 + ecb2;
if num_blocks == 0 {
return Err(Error::InvalidData(format!(
"qrcode_native: zero-block layout for metric {metric_idx} EC {ec_level}",
)));
}
let dcpb = dcws / num_blocks;
let ecpb = ncws / num_blocks - dcpb;
Ok(BlockLayout {
ncws,
dcws,
ecpb,
ecb1,
ecb2,
dcpb,
rbit,
lc4b,
})
}
pub(crate) fn split_data_blocks<'a>(data: &'a [u8], layout: &BlockLayout) -> Vec<&'a [u8]> {
let dcpb = layout.dcpb as usize;
let ecb1 = layout.ecb1 as usize;
let ecb2 = layout.ecb2 as usize;
let mut blocks = Vec::with_capacity(ecb1 + ecb2);
let mut offset = 0;
for _ in 0..ecb1 {
blocks.push(&data[offset..offset + dcpb]);
offset += dcpb;
}
for _ in 0..ecb2 {
blocks.push(&data[offset..offset + dcpb + 1]);
offset += dcpb + 1;
}
blocks
}
pub(crate) fn interleave_blocks(data_blocks: &[&[u8]], ecc_blocks: &[Vec<u8>]) -> Vec<u8> {
assert_eq!(
data_blocks.len(),
ecc_blocks.len(),
"must have same number of data and ECC blocks"
);
let max_data_cols = data_blocks.iter().map(|b| b.len()).max().unwrap_or(0);
let ecc_cols = ecc_blocks.first().map(|b| b.len()).unwrap_or(0);
let total = data_blocks.iter().map(|b| b.len()).sum::<usize>()
+ ecc_blocks.iter().map(|b| b.len()).sum::<usize>();
let mut out = Vec::with_capacity(total);
for col in 0..max_data_cols {
for block in data_blocks {
if col < block.len() {
out.push(block[col]);
}
}
}
for col in 0..ecc_cols {
for block in ecc_blocks {
out.push(block[col]);
}
}
out
}
pub(crate) fn apply_lc4b_nibble_fixup(stream: &mut [u8], dcws: u32, ncws: u32) {
let dcws = dcws as usize;
let ncws = ncws as usize;
if ncws == 0 {
return;
}
stream[dcws - 1] >>= 4;
for i in (dcws - 1)..(ncws - 1) {
stream[i] = (stream[i] & 0x0F) << 4;
stream[i] |= (stream[i + 1] >> 4) & 0x0F;
}
stream[ncws - 1] = (stream[ncws - 1] & 0x0F) << 4;
}
pub(crate) fn build_codeword_stream(
padded_data: &[u8],
metric_idx: usize,
ec_level: u8,
) -> Result<Vec<u8>, Error> {
let layout = block_layout(metric_idx, ec_level)?;
if padded_data.len() != layout.dcws as usize {
return Err(Error::InvalidData(format!(
"qrcode_native: padded_data length {} does not match dcws {}",
padded_data.len(),
layout.dcws
)));
}
let data_blocks = split_data_blocks(padded_data, &layout);
let ecc_blocks: Vec<Vec<u8>> = data_blocks
.iter()
.map(|b| qr_rs_block_ecc(b, layout.ecpb as usize))
.collect();
let mut stream = interleave_blocks(&data_blocks, &ecc_blocks);
if layout.rbit > 0 {
stream.push(0);
}
if layout.lc4b {
apply_lc4b_nibble_fixup(&mut stream, layout.dcws, layout.ncws);
}
Ok(stream)
}
pub(crate) const PIXS_UNSET: i8 = -1;
pub(crate) fn init_pixs_matrix(rows: u16, cols: u16) -> Vec<i8> {
vec![PIXS_UNSET; rows as usize * cols as usize]
}
#[inline]
pub(crate) fn qmv(row: usize, col: usize, cols: usize) -> usize {
row * cols + col
}
#[inline]
fn pixs_set(pixs: &mut [i8], row: usize, col: usize, cols: usize, value: i8) {
if col >= cols {
return;
}
let idx = qmv(row, col, cols);
if idx < pixs.len() {
pixs[idx] = value;
}
}
pub(crate) fn place_timing_patterns(
pixs: &mut [i8],
layout_id: u8,
rows: u16,
cols: u16,
fimax: u16,
fimas: u16,
) {
let cols_u = cols as usize;
let rows_u = rows as usize;
let bin = layout_id as usize;
let format = if bin <= 2 {
Format::Full
} else if bin <= 6 {
Format::Micro
} else {
Format::Rmqr
};
match format {
Format::Full => {
for i in 8..=cols_u.saturating_sub(9) {
let bit = ((i + 1) % 2) as i8;
pixs_set(pixs, 6, i, cols_u, bit);
pixs_set(pixs, i, 6, cols_u, bit);
}
let _ = (fimax, fimas);
}
Format::Micro => {
for i in 8..cols_u {
let bit = ((i + 1) % 2) as i8;
pixs_set(pixs, 0, i, cols_u, bit);
pixs_set(pixs, i, 0, cols_u, bit);
}
let _ = (fimax, fimas);
}
Format::Rmqr => {
for i in 3..=cols_u.saturating_sub(4) {
let bit = ((i + 1) % 2) as i8;
pixs_set(pixs, 0, i, cols_u, bit);
pixs_set(pixs, rows_u - 1, i, cols_u, bit);
}
for i in 3..=rows_u.saturating_sub(4) {
let bit = ((i + 1) % 2) as i8;
pixs_set(pixs, i, 0, cols_u, bit);
pixs_set(pixs, i, cols_u - 1, cols_u, bit);
}
if fimax != NA {
let step = if fimas == NA {
0i32
} else {
(fimas as i32) - (fimax as i32)
};
let edge_end = cols_u.saturating_sub(13);
let mut i = (fimax as usize).saturating_sub(1);
loop {
if i > edge_end {
break;
}
for j in 3..=rows_u.saturating_sub(4) {
let bit = ((j + 1) % 2) as i8;
pixs_set(pixs, j, i, cols_u, bit);
}
if step <= 0 {
break;
}
i += step as usize;
}
}
}
}
}
pub(crate) const FINDER_PATTERN: [[u8; 7]; 7] = [
[1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1],
];
pub(crate) const FCORPAT_RMQR: [[i8; 8]; 8] = [
[1, 1, 1, 9, 9, 9, 9, 9],
[1, 0, 9, 9, 9, 9, 9, 9],
[1, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9],
];
pub(crate) const FSUBPAT_RMQR: [[i8; 8]; 8] = [
[1, 1, 1, 1, 1, 9, 9, 9],
[1, 0, 0, 0, 1, 9, 9, 9],
[1, 0, 1, 0, 1, 9, 9, 9],
[1, 0, 0, 0, 1, 9, 9, 9],
[1, 1, 1, 1, 1, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9],
];
pub(crate) const FPAT_RMQR: [[i8; 8]; 8] = [
[1, 1, 1, 1, 1, 1, 1, 0],
[1, 0, 0, 0, 0, 0, 1, 0],
[1, 0, 1, 1, 1, 0, 1, 0],
[1, 0, 1, 1, 1, 0, 1, 0],
[1, 0, 1, 1, 1, 0, 1, 0],
[1, 0, 0, 0, 0, 0, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
];
pub(crate) fn place_finder_patterns(pixs: &mut [i8], layout_id: u8, rows: u16, cols: u16) {
let cols_u = cols as usize;
let rows_u = rows as usize;
let bin = layout_id as usize;
let format = if bin <= 2 {
Format::Full
} else if bin <= 6 {
Format::Micro
} else {
Format::Rmqr
};
const NULL_PAT: [[i8; 8]; 8] = [[9; 8]; 8];
let (tl, tr, bl, br) = match format {
Format::Full => (&FPAT_RMQR, &FPAT_RMQR, &FPAT_RMQR, &NULL_PAT),
Format::Micro => (&FPAT_RMQR, &NULL_PAT, &NULL_PAT, &NULL_PAT),
Format::Rmqr => (&FPAT_RMQR, &FCORPAT_RMQR, &FCORPAT_RMQR, &FSUBPAT_RMQR),
};
for y in 0..8usize {
for x in 0..8usize {
let fpb0 = tl[y][x];
if fpb0 != 9 && y < rows_u {
pixs_set(pixs, y, x, cols_u, fpb0);
}
let fpb1 = tr[y][x];
if fpb1 != 9 && x < cols_u {
pixs_set(pixs, y, cols_u - x - 1, cols_u, fpb1);
}
let fpb2 = bl[y][x];
if fpb2 != 9 && y < rows_u {
pixs_set(pixs, rows_u - y - 1, x, cols_u, fpb2);
}
let fpb3 = br[y][x];
if fpb3 != 9 && x < cols_u && y < rows_u {
pixs_set(pixs, rows_u - y - 1, cols_u - x - 1, cols_u, fpb3);
}
}
}
}
pub(crate) const ALGN_SKIP: i8 = 9;
pub(crate) const ALIGNMENT_PATTERN_FULL: [[i8; 5]; 5] = [
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 1],
[1, 0, 1, 0, 1],
[1, 0, 0, 0, 1],
[1, 1, 1, 1, 1],
];
pub(crate) const ALIGNMENT_PATTERN_RMQR: [[i8; 5]; 5] = [
[1, 1, 1, ALGN_SKIP, ALGN_SKIP],
[1, 0, 1, ALGN_SKIP, ALGN_SKIP],
[1, 1, 1, ALGN_SKIP, ALGN_SKIP],
[ALGN_SKIP, ALGN_SKIP, ALGN_SKIP, ALGN_SKIP, ALGN_SKIP],
[ALGN_SKIP, ALGN_SKIP, ALGN_SKIP, ALGN_SKIP, ALGN_SKIP],
];
fn put_alignment_pattern(
pixs: &mut [i8],
px: usize,
py: usize,
cols: usize,
pattern: &[[i8; 5]; 5],
) {
for (pb, row) in pattern.iter().enumerate() {
for (pa, &cell) in row.iter().enumerate() {
if cell != ALGN_SKIP {
pixs_set(pixs, py + pb, px + pa, cols, cell);
}
}
}
}
pub(crate) fn place_alignment_patterns(
pixs: &mut [i8],
layout_id: u8,
rows: u16,
cols: u16,
fimax: u16,
fimas: u16,
) {
if fimax == NA {
return;
}
let cols_u = cols as usize;
let rows_u = rows as usize;
let bin = layout_id as usize;
let format = if bin <= 2 {
Format::Full
} else if bin <= 6 {
Format::Micro
} else {
Format::Rmqr
};
match format {
Format::Full => {
let pattern = &ALIGNMENT_PATTERN_FULL;
let start = fimax.saturating_sub(2) as usize;
if fimas == NA {
put_alignment_pattern(pixs, start, start, cols_u, pattern);
} else {
let step = (fimas as i32) - (fimax as i32); if step <= 0 {
return;
}
let step = step as usize;
let edge_end = cols_u.saturating_sub(13);
let mut i = start;
while i <= edge_end {
put_alignment_pattern(pixs, i, 4, cols_u, pattern);
put_alignment_pattern(pixs, 4, i, cols_u, pattern);
i += step;
}
let inner_end_x = cols_u.saturating_sub(9);
let inner_end_y = rows_u.saturating_sub(9);
let mut x = start;
while x <= inner_end_x {
let mut y = start;
while y <= inner_end_y {
put_alignment_pattern(pixs, x, y, cols_u, pattern);
y += step;
}
x += step;
}
}
}
Format::Rmqr => {
let pattern = &ALIGNMENT_PATTERN_RMQR;
if fimas == NA {
let start = fimax.saturating_sub(2) as usize;
put_alignment_pattern(pixs, start, 0, cols_u, pattern);
put_alignment_pattern(pixs, start, rows_u.saturating_sub(3), cols_u, pattern);
} else {
let step = (fimas as i32) - (fimax as i32);
if step <= 0 {
return;
}
let step = step as usize;
let edge_end = cols_u.saturating_sub(13);
let mut i = fimax.saturating_sub(2) as usize;
while i <= edge_end {
put_alignment_pattern(pixs, i, 0, cols_u, pattern);
put_alignment_pattern(pixs, i, rows_u.saturating_sub(3), cols_u, pattern);
i += step;
}
}
}
Format::Micro => {
}
}
}
const FORMAT_INFO_RESERVATION_VALUE: i8 = 1;
fn place_format_info_reservation_full(pixs: &mut [i8], rows: u16, cols: u16) {
let cols_u = cols as usize;
let rows_u = rows as usize;
let tl_cells = [
(0, 8),
(1, 8),
(2, 8),
(3, 8),
(4, 8),
(5, 8),
(7, 8),
(8, 8),
(8, 7),
(8, 5),
(8, 4),
(8, 3),
(8, 2),
(8, 1),
(8, 0),
];
for &(r, c) in &tl_cells {
pixs_set(pixs, r, c, cols_u, FORMAT_INFO_RESERVATION_VALUE);
}
for k in 0..8 {
pixs_set(
pixs,
8,
cols_u - 1 - k,
cols_u,
FORMAT_INFO_RESERVATION_VALUE,
);
}
for k in 1..8 {
pixs_set(
pixs,
rows_u - 8 + k,
8,
cols_u,
FORMAT_INFO_RESERVATION_VALUE,
);
}
pixs_set(pixs, rows_u - 8, 8, cols_u, 0);
}
fn place_format_info_reservation_micro(pixs: &mut [i8], rows: u16, cols: u16) {
let cols_u = cols as usize;
let _ = rows; let cells = [
(1, 8),
(2, 8),
(3, 8),
(4, 8),
(5, 8),
(6, 8),
(7, 8),
(8, 8),
(8, 7),
(8, 6),
(8, 5),
(8, 4),
(8, 3),
(8, 2),
(8, 1),
];
for &(r, c) in &cells {
pixs_set(pixs, r, c, cols_u, FORMAT_INFO_RESERVATION_VALUE);
}
}
pub(crate) fn place_format_info_reservation(
pixs: &mut [i8],
layout_id: u8,
rows: u16,
cols: u16,
) -> usize {
let bin = layout_id as usize;
let format = if bin <= 2 {
Format::Full
} else if bin <= 6 {
Format::Micro
} else {
Format::Rmqr
};
match format {
Format::Full => {
place_format_info_reservation_full(pixs, rows, cols);
31
}
Format::Micro => {
place_format_info_reservation_micro(pixs, rows, cols);
15
}
Format::Rmqr => {
place_format_info_reservation_rmqr(pixs, rows, cols);
36
}
}
}
#[allow(clippy::type_complexity)]
pub(crate) const RMQR_FORMATFIMMAP_CLUSTERS: [(u8, u8, u8, u8); 18] = [
(11, 3, 3, 6),
(11, 2, 4, 6),
(11, 1, 5, 6),
(10, 5, 6, 2),
(10, 4, 6, 3),
(10, 3, 6, 4),
(10, 2, 6, 5),
(10, 1, 6, 6),
(9, 5, 7, 2),
(9, 4, 7, 3),
(9, 3, 7, 4),
(9, 2, 7, 5),
(9, 1, 7, 6),
(8, 5, 8, 2),
(8, 4, 8, 3),
(8, 3, 8, 4),
(8, 2, 8, 5),
(8, 1, 8, 6),
];
pub(crate) fn rmqr_formatfimmap_pairs(rows: u16, cols: u16) -> [((u16, u16), (u16, u16)); 18] {
let mut out = [((0u16, 0u16), (0u16, 0u16)); 18];
for (i, &(tl_col, tl_row, dup_dx, dup_dy)) in RMQR_FORMATFIMMAP_CLUSTERS.iter().enumerate() {
let dup_col = cols.saturating_sub(dup_dx as u16);
let dup_row = rows.saturating_sub(dup_dy as u16);
out[i] = ((tl_row as u16, tl_col as u16), (dup_row, dup_col));
}
out
}
fn place_format_info_reservation_rmqr(pixs: &mut [i8], rows: u16, cols: u16) {
let cols_u = cols as usize;
let pairs = rmqr_formatfimmap_pairs(rows, cols);
for &((tl_row, tl_col), (dup_row, dup_col)) in &pairs {
pixs_set(
pixs,
tl_row as usize,
tl_col as usize,
cols_u,
FORMAT_INFO_RESERVATION_VALUE,
);
pixs_set(
pixs,
dup_row as usize,
dup_col as usize,
cols_u,
FORMAT_INFO_RESERVATION_VALUE,
);
}
}
pub(crate) fn place_version_info_reservation(
pixs: &mut [i8],
layout_id: u8,
rows: u16,
cols: u16,
version: u8,
) -> usize {
let bin = layout_id as usize;
let format = if bin <= 2 {
Format::Full
} else if bin <= 6 {
Format::Micro
} else {
Format::Rmqr
};
if !matches!(format, Format::Full) || version < 7 {
return 0;
}
let cols_u = cols as usize;
let rows_u = rows as usize;
for r in (rows_u - 11)..=(rows_u - 9) {
for c in 0..=5 {
pixs_set(pixs, r, c, cols_u, 0);
}
}
for r in 0..=5 {
for c in (cols_u - 11)..=(cols_u - 9) {
pixs_set(pixs, r, c, cols_u, 0);
}
}
36
}
pub(crate) fn walk_codeword_positions(
layout_id: u8,
rows: u16,
cols: u16,
pixs: &[i8],
) -> Vec<usize> {
let cols_u = cols as usize;
let rows_i = rows as i32;
let bin = layout_id as usize;
let format = if bin <= 2 {
Format::Full
} else if bin <= 6 {
Format::Micro
} else {
Format::Rmqr
};
let start_offset = if matches!(format, Format::Rmqr) { 2 } else { 1 };
let mut posx = cols_u as i32 - start_offset;
let mut posy = rows_i - 1;
let mut dir: i32 = -1; let mut col: i32 = 1; let mut visited: Vec<usize> = Vec::new();
while posx >= 0 {
let idx = qmv(posy as usize, posx as usize, cols_u);
if idx < pixs.len() && pixs[idx] == PIXS_UNSET {
visited.push(idx);
}
if col == 1 {
col = 0;
posx -= 1;
} else {
col = 1;
posx += 1;
posy += dir;
if posy < 0 || posy >= rows_i {
dir = -dir;
posy += dir;
posx -= 2;
if matches!(format, Format::Full) && posx == 6 {
posx -= 1;
}
}
}
}
visited
}
pub(crate) fn place_codewords_at(
pixs: &mut [i8],
positions: &[usize],
cws: &[u8],
) -> Result<(), Error> {
let total_bits = cws.len() * 8;
let placeable = positions.len().min(total_bits);
if total_bits > positions.len() + 8 {
return Err(Error::InvalidData(format!(
"qrcode_native: codeword stream ({total_bits} bits) exceeds available positions ({}) by more than one rbit-padded codeword",
positions.len()
)));
}
for (i, &pos) in positions.iter().enumerate().take(placeable) {
let byte = cws[i / 8];
let bit = (byte >> (7 - (i % 8))) & 1;
if pos < pixs.len() {
pixs[pos] = bit as i8;
}
}
Ok(())
}
pub(crate) const MASK_FUNCS: [fn(usize, usize) -> bool; 8] = [
|row, col| (row + col) % 2 == 0,
|row, _col| row % 2 == 0,
|_row, col| col % 3 == 0,
|row, col| (row + col) % 3 == 0,
|row, col| (row / 2 + col / 3) % 2 == 0,
|row, col| (row * col) % 2 + (row * col) % 3 == 0,
|row, col| ((row * col) % 2 + (row * col) % 3) % 2 == 0,
|row, col| ((row + col) % 2 + (row * col) % 3) % 2 == 0,
];
pub(crate) fn mask_candidates(layout_id: u8) -> &'static [u8] {
let bin = layout_id as usize;
if bin <= 2 {
&[0, 1, 2, 3, 4, 5, 6, 7]
} else if bin <= 6 {
&[1, 4, 6, 7]
} else {
&[4]
}
}
pub(crate) fn apply_mask_to_data_cells(pixs: &mut [i8], mask_idx: u8, rows: u16, cols: u16) {
debug_assert!(mask_idx < 8, "mask_idx must be 0..=7");
let mask = MASK_FUNCS[mask_idx as usize];
let cu = cols as usize;
for r in 0..(rows as usize) {
for c in 0..cu {
let idx = qmv(r, c, cu);
if (pixs[idx] == 0 || pixs[idx] == 1) && mask(r, c) {
pixs[idx] ^= 1;
}
}
}
}
pub(crate) fn apply_mask_at_positions(
pixs: &mut [i8],
data_positions: &[usize],
mask_idx: u8,
cols: u16,
) {
debug_assert!(mask_idx < 8, "mask_idx must be 0..=7");
let mask = MASK_FUNCS[mask_idx as usize];
let cu = cols as usize;
for &pos in data_positions {
let row = pos / cu;
let col = pos % cu;
if (pixs[pos] == 0 || pixs[pos] == 1) && mask(row, col) {
pixs[pos] ^= 1;
}
}
}
pub(crate) fn evaluate_mask_micro(pixs: &[i8], rows: u16, cols: u16) -> i32 {
let rows_u = rows as usize;
let cols_u = cols as usize;
let mut dk_rhs: i32 = 0; let mut dk_bot: i32 = 0; for i in 1..=cols_u.saturating_sub(1) {
let cell_rhs = pixs[qmv(i, cols_u - 1, cols_u)];
if cell_rhs == 1 {
dk_rhs += 1;
}
let cell_bot = pixs[qmv(rows_u - 1, i, cols_u)];
if cell_bot == 1 {
dk_bot += 1;
}
}
let (lo, hi) = if dk_rhs <= dk_bot {
(dk_rhs, dk_bot)
} else {
(dk_bot, dk_rhs)
};
-(lo * 16 + hi)
}
pub(crate) fn select_best_micro_mask(
data_template: &[i8],
data_positions: &[usize],
layout_id: u8,
rows: u16,
cols: u16,
) -> Result<(u8, Vec<i8>), Error> {
let bin = layout_id as usize;
if !(3..=6).contains(&bin) {
return Err(Error::InvalidData(format!(
"qrcode_native: select_best_micro_mask called with non-Micro layout_id {layout_id}"
)));
}
let candidates = mask_candidates(layout_id);
let mut best: Option<(u8, i32, Vec<i8>)> = None;
for (idx, &m) in candidates.iter().enumerate() {
let mut trial = data_template.to_vec();
apply_mask_at_positions(&mut trial, data_positions, m, cols);
let score = evaluate_mask_micro(&trial, rows, cols);
let cand_idx = idx as u8;
match &best {
None => best = Some((cand_idx, score, trial)),
Some(b) if score < b.1 => best = Some((cand_idx, score, trial)),
_ => {}
}
}
let (cand_idx, _score, pixs) = best.expect("at least one mask candidate for Micro QR");
Ok((cand_idx, pixs))
}
fn rle_run(cells: impl Iterator<Item = i8>) -> Vec<u32> {
let mut runs: Vec<u32> = Vec::new();
let mut last_color: i8 = 0;
let mut current: u32 = 0;
for cell in cells {
let color = if cell == 1 { 1 } else { 0 };
if color == last_color {
current += 1;
} else {
runs.push(current);
current = 1;
last_color = color;
}
}
runs.push(current);
runs
}
pub(crate) fn evalfull_n1n3(scrle: &[u32]) -> (u32, u32) {
let mut n1: u32 = 0;
for &run in scrle {
if run >= 5 {
n1 += run - 2;
}
}
let mut n3: u32 = 0;
if scrle.len() >= 6 {
let mut j: usize = 3;
while j + 3 <= scrle.len() {
if scrle[j] % 3 == 0 {
let fact = scrle[j] / 3;
if fact > 0
&& scrle[j - 2] == fact
&& scrle[j - 1] == fact
&& scrle[j + 1] == fact
&& scrle[j + 2] == fact
{
let at_start = j == 3;
let at_end = j + 4 >= scrle.len();
let pre_quiet = scrle[j - 3] >= 4;
let post_quiet = (j + 3 < scrle.len()) && scrle[j + 3] >= 4;
if at_start || at_end || pre_quiet || post_quiet {
n3 += 40;
}
}
}
j += 2;
}
}
(n1, n3)
}
fn evalfull_n2(pixs: &[i8], rows: u16, cols: u16) -> u32 {
let rows_u = rows as usize;
let cols_u = cols as usize;
if rows_u < 2 || cols_u < 2 {
return 0;
}
let mut count: u32 = 0;
for r in 0..(rows_u - 1) {
for c in 0..(cols_u - 1) {
let a = pixs[qmv(r, c, cols_u)];
let b = pixs[qmv(r, c + 1, cols_u)];
let d = pixs[qmv(r + 1, c, cols_u)];
let e = pixs[qmv(r + 1, c + 1, cols_u)];
if a == b && a == d && a == e && (a == 0 || a == 1) {
count += 1;
}
}
}
count * 3
}
fn evalfull_n4(pixs: &[i8], rows: u16, cols: u16) -> u32 {
let rows_u = rows as usize;
let cols_u = cols as usize;
let total = rows_u * cols_u;
if total == 0 {
return 0;
}
let dark: u32 = pixs.iter().take(total).filter(|&&c| c == 1).count() as u32;
let denom = (cols_u * cols_u) as u32;
let percent = (dark * 100) / denom;
let deviation = percent.abs_diff(50);
(deviation / 5) * 10
}
pub(crate) fn evaluate_mask_full(pixs: &[i8], rows: u16, cols: u16) -> u32 {
let rows_u = rows as usize;
let cols_u = cols as usize;
let mut n1_total: u32 = 0;
let mut n3_total: u32 = 0;
for c in 0..cols_u {
let col_rle = rle_run((0..rows_u).map(|r| pixs[qmv(r, c, cols_u)]));
let (n1, n3) = evalfull_n1n3(&col_rle);
n1_total += n1;
n3_total += n3;
}
for r in 0..rows_u {
let row_rle = rle_run((0..cols_u).map(|c| pixs[qmv(r, c, cols_u)]));
let (n1, n3) = evalfull_n1n3(&row_rle);
n1_total += n1;
n3_total += n3;
}
let n2 = evalfull_n2(pixs, rows, cols);
let n4 = evalfull_n4(pixs, rows, cols);
n1_total + n2 + n3_total + n4
}
pub(crate) fn select_best_full_mask(
data_template: &[i8],
data_positions: &[usize],
layout_id: u8,
rows: u16,
cols: u16,
) -> Result<(u8, Vec<i8>), Error> {
let bin = layout_id as usize;
if bin > 2 {
return Err(Error::InvalidData(format!(
"qrcode_native: select_best_full_mask called with non-Full layout_id {layout_id}"
)));
}
let candidates = mask_candidates(layout_id);
let mut best: Option<(u8, u32, Vec<i8>)> = None;
for &m in candidates {
let mut trial = data_template.to_vec();
apply_mask_at_positions(&mut trial, data_positions, m, cols);
let score = evaluate_mask_full(&trial, rows, cols);
match &best {
None => best = Some((m, score, trial)),
Some(b) if score < b.1 => best = Some((m, score, trial)),
_ => {}
}
}
let (m, _score, pixs) = best.expect("at least one mask candidate for Full QR");
Ok((m, pixs))
}
pub(crate) fn select_best_mask(
data_template: &[i8],
data_positions: &[usize],
layout_id: u8,
rows: u16,
cols: u16,
) -> Result<(u8, Vec<i8>), Error> {
let bin = layout_id as usize;
if bin <= 2 {
select_best_full_mask(data_template, data_positions, layout_id, rows, cols)
} else if bin <= 6 {
select_best_micro_mask(data_template, data_positions, layout_id, rows, cols)
} else {
let mut pixs = data_template.to_vec();
apply_mask_at_positions(&mut pixs, data_positions, 4, cols);
Ok((4, pixs))
}
}
pub(crate) const QRCODE_EC_INDICATOR_FULL: [u8; 4] = [1, 0, 3, 2];
fn format_info_pairs_full(rows: u16, cols: u16) -> [((usize, usize), (usize, usize)); 15] {
let rows_u = rows as usize;
let cols_u = cols as usize;
[
((8, 0), (rows_u - 1, 8)),
((8, 1), (rows_u - 2, 8)),
((8, 2), (rows_u - 3, 8)),
((8, 3), (rows_u - 4, 8)),
((8, 4), (rows_u - 5, 8)),
((8, 5), (rows_u - 6, 8)),
((8, 7), (rows_u - 7, 8)),
((8, 8), (8, cols_u - 8)),
((7, 8), (8, cols_u - 7)),
((5, 8), (8, cols_u - 6)),
((4, 8), (8, cols_u - 5)),
((3, 8), (8, cols_u - 4)),
((2, 8), (8, cols_u - 3)),
((1, 8), (8, cols_u - 2)),
((0, 8), (8, cols_u - 1)),
]
}
const FORMAT_INFO_POSITIONS_MICRO: [(usize, usize); 15] = [
(8, 1),
(8, 2),
(8, 3),
(8, 4),
(8, 5),
(8, 6),
(8, 7),
(8, 8),
(7, 8),
(6, 8),
(5, 8),
(4, 8),
(3, 8),
(2, 8),
(1, 8),
];
pub(crate) fn compute_format_info_bits(
layout_id: u8,
ec_level: u8,
mask_index: u8,
) -> Result<u16, Error> {
if ec_level >= 4 {
return Err(Error::InvalidData(format!(
"qrcode_native: ec_level {ec_level} out of range (0..=3 = L/M/Q/H)"
)));
}
let bin = layout_id as usize;
if bin <= 2 {
if mask_index >= 8 {
return Err(Error::InvalidData(format!(
"qrcode_native: Full QR mask_index {mask_index} out of range (0..=7)"
)));
}
let ec_id = QRCODE_EC_INDICATOR_FULL[ec_level as usize];
let data = (ec_id << 3) | mask_index;
let bch = bch15_5_encode(data);
Ok(bch ^ FORMAT_INFO_MASK_FULL)
} else if bin <= 6 {
if mask_index >= 4 {
return Err(Error::InvalidData(format!(
"qrcode_native: Micro QR mask_index {mask_index} out of range (0..=3)"
)));
}
let sym_id = micro_sym_id(layout_id, ec_level)?;
let data = (sym_id << 2) | mask_index;
let bch = bch15_5_encode(data);
Ok(bch ^ FORMAT_INFO_MASK_MICRO)
} else {
Err(Error::InvalidData(
"qrcode_native: compute_format_info_bits handles Full/Micro layouts only — rMQR layouts (layout_id >= 7) use rmqr_fmtval1/rmqr_fmtval2 via write_format_info_bits".to_string(),
))
}
}
fn micro_sym_id(layout_id: u8, ec_level: u8) -> Result<u8, Error> {
let table: [&[Option<u8>]; 4] = [
&[Some(0), None, None, None], &[Some(1), Some(2), None, None], &[Some(3), Some(4), None, None], &[Some(5), Some(6), Some(7), None], ];
let mi = (layout_id as usize).checked_sub(3).ok_or_else(|| {
Error::InvalidData(format!(
"qrcode_native: layout_id {layout_id} is not a Micro QR layout"
))
})?;
if mi >= 4 {
return Err(Error::InvalidData(format!(
"qrcode_native: layout_id {layout_id} is not a Micro QR layout"
)));
}
let row = table[mi];
let ec_idx = ec_level as usize;
if ec_idx >= row.len() {
return Err(Error::InvalidData(format!(
"qrcode_native: Micro QR layout_id {layout_id} doesn't support EC level {ec_level}"
)));
}
row[ec_idx].ok_or_else(|| {
Error::InvalidData(format!(
"qrcode_native: Micro QR layout_id {layout_id} doesn't support EC level {ec_level}"
))
})
}
pub(crate) fn write_format_info_bits(
pixs: &mut [i8],
layout_id: u8,
rows: u16,
cols: u16,
ec_level: u8,
mask_index: u8,
) -> Result<(), Error> {
let bin = layout_id as usize;
let cols_u = cols as usize;
if bin <= 2 {
let fmtval = compute_format_info_bits(layout_id, ec_level, mask_index)?;
let pairs = format_info_pairs_full(rows, cols);
for (i, &((r1, c1), (r2, c2))) in pairs.iter().enumerate() {
let bit = ((fmtval >> (14 - i)) & 1) as i8;
pixs_set(pixs, r1, c1, cols_u, bit);
pixs_set(pixs, r2, c2, cols_u, bit);
}
write_dark_module_full(pixs, rows, cols);
} else if bin <= 6 {
let fmtval = compute_format_info_bits(layout_id, ec_level, mask_index)?;
for (i, &(r, c)) in FORMAT_INFO_POSITIONS_MICRO.iter().enumerate() {
let bit = ((fmtval >> (14 - i)) & 1) as i8;
pixs_set(pixs, r, c, cols_u, bit);
}
} else {
let ec_id = QRCODE_EC_INDICATOR_RMQR
.get(ec_level as usize)
.copied()
.unwrap_or(-1);
if ec_id < 0 {
return Err(Error::InvalidOption(format!(
"qrcode_native: rMQR supports only EC levels M (1) and H (3); got {ec_level}"
)));
}
let verind = bin
.checked_sub(7)
.ok_or_else(|| Error::InvalidData(format!("rMQR layout_id underflow ({bin})")))?
as u8;
if verind >= 32 {
return Err(Error::InvalidData(format!(
"qrcode_native: rMQR verind {verind} out of range (0..32)"
)));
}
let data = (ec_id as u8) * 32 + verind;
let f1 = rmqr_fmtval1(data);
let f2 = rmqr_fmtval2(data);
let _ = mask_index; let pairs = rmqr_formatfimmap_pairs(rows, cols);
for (i, &((r1, c1), (r2, c2))) in pairs.iter().enumerate() {
let b1 = ((f1 >> (17 - i)) & 1) as i8;
let b2 = ((f2 >> (17 - i)) & 1) as i8;
pixs_set(pixs, r1 as usize, c1 as usize, cols_u, b1);
pixs_set(pixs, r2 as usize, c2 as usize, cols_u, b2);
}
}
Ok(())
}
pub(crate) fn write_dark_module_full(pixs: &mut [i8], rows: u16, cols: u16) {
let rows_u = rows as usize;
let cols_u = cols as usize;
pixs_set(pixs, rows_u - 8, 8, cols_u, 1);
}
fn version_info_pairs_full(cols: u16) -> [((usize, usize), (usize, usize)); 18] {
let cu = cols as usize;
let mut out = [((0usize, 0usize), (0usize, 0usize)); 18];
for (i, slot) in out.iter_mut().enumerate() {
let row = 5 - (i / 3);
let off = 9 + (i % 3);
*slot = ((row, cu - off), (cu - off, row));
}
out
}
pub(crate) fn write_version_info_bits(
pixs: &mut [i8],
layout_id: u8,
rows: u16,
cols: u16,
version: u8,
) -> Result<usize, Error> {
let bin = layout_id as usize;
let format = if bin <= 2 {
Format::Full
} else if bin <= 6 {
Format::Micro
} else {
Format::Rmqr
};
if !matches!(format, Format::Full) || version < 7 {
return Ok(0);
}
if version > 40 {
return Err(Error::InvalidData(format!(
"qrcode_native: invalid Full QR version {version} (must be 1..=40)"
)));
}
let _ = rows; let verval = bch18_6_encode(version);
let cu = cols as usize;
let pairs = version_info_pairs_full(cols);
for (i, &((r1, c1), (r2, c2))) in pairs.iter().enumerate() {
let bit = ((verval >> (17 - i)) & 1) as i8;
pixs_set(pixs, r1, c1, cu, bit);
pixs_set(pixs, r2, c2, cu, bit);
}
Ok(36)
}
pub(crate) fn encode_full_qr(msg: &[u8], version: u8, ec_level: u8) -> Result<BitMatrix, Error> {
if !(1..=40).contains(&version) {
return Err(Error::InvalidData(format!(
"qrcode_native: Full QR version {version} out of range (1..=40)"
)));
}
let metric_idx = 4 + (version - 1) as usize;
encode_qr_at_metric(msg, metric_idx, ec_level)
}
pub(crate) fn encode_qr_at_metric(
msg: &[u8],
metric_idx: usize,
ec_level: u8,
) -> Result<BitMatrix, Error> {
encode_qr_at_metric_with_fnc1(msg, metric_idx, ec_level, false)
}
pub(crate) fn encode_qr_at_metric_with_fnc1(
msg: &[u8],
metric_idx: usize,
ec_level: u8,
fnc1first: bool,
) -> Result<BitMatrix, Error> {
if ec_level >= 4 {
return Err(Error::InvalidData(format!(
"qrcode_native: ec_level {ec_level} out of range (0..=3 = L/M/Q/H)"
)));
}
if metric_idx >= FULL_METRICS.len() {
return Err(Error::InvalidData(format!(
"qrcode_native: metric_idx {metric_idx} out of range (0..{})",
FULL_METRICS.len()
)));
}
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let version = if matches!(m.format, Format::Full) {
(metric_idx - 3) as u8
} else {
0
};
let layout = block_layout(metric_idx, ec_level)?;
let segments = select_segments(msg, layout_id, fnc1first);
let bits = compose_segments(msg, &segments, layout_id, fnc1first)?;
let padded_data = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws)?;
let stream = build_codeword_stream(&padded_data, metric_idx, ec_level)?;
let mut pixs = init_pixs_matrix(rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
place_version_info_reservation(&mut pixs, layout_id, rows, cols, version);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
place_codewords_at(&mut pixs, &positions, &stream)?;
let (mask, masked_pixs) = select_best_mask(&pixs, &positions, layout_id, rows, cols)?;
let mut pixs = masked_pixs;
write_format_info_bits(&mut pixs, layout_id, rows, cols, ec_level, mask)?;
write_version_info_bits(&mut pixs, layout_id, rows, cols, version)?;
let rows_u = rows as usize;
let cols_u = cols as usize;
let mut matrix = BitMatrix::new(cols_u, rows_u);
for r in 0..rows_u {
for c in 0..cols_u {
if pixs[qmv(r, c, cols_u)] == 1 {
matrix.set(c, r, true);
}
}
}
Ok(matrix)
}
pub(crate) fn encode_micro_qr(
msg: &[u8],
micro_idx: usize,
ec_level: u8,
) -> Result<BitMatrix, Error> {
if micro_idx >= 4 {
return Err(Error::InvalidData(format!(
"qrcode_native: micro_idx {micro_idx} out of range (0..4 = M1..M4)"
)));
}
encode_qr_at_metric(msg, micro_idx, ec_level)
}
pub fn encode_rmqr(text: &[u8], version_str: &str, ec_level: u8) -> Result<BitMatrix, Error> {
let metric_idx = FULL_METRICS
.iter()
.position(|m| matches!(m.format, Format::Rmqr) && m.version_str == version_str)
.ok_or_else(|| {
Error::InvalidOption(format!(
"qrcode_native: unknown rMQR version `{version_str}` (expected R7x43, R7x59, ..., R17x139)"
))
})?;
encode_qr_at_metric(text, metric_idx, ec_level)
}
pub(crate) fn encode(input: &[u8]) -> Result<BitMatrix, Error> {
if input.is_empty() {
return Err(Error::InvalidData(
"qrcode_native: empty input is not encodable".to_string(),
));
}
let (version, ec_level) = auto_select_full_qr_version(input, 1)?;
encode_full_qr(input, version, ec_level)
}
pub fn encode_with_options(input: &[u8], opts: &crate::Options) -> Result<BitMatrix, Error> {
encode_with_options_fnc1(input, opts, false)
}
pub fn encode_gs1_qrcode(input: &[u8], opts: &crate::Options) -> Result<BitMatrix, Error> {
encode_with_options_fnc1(input, opts, true)
}
fn encode_with_options_fnc1(
input: &[u8],
opts: &crate::Options,
fnc1first: bool,
) -> Result<BitMatrix, Error> {
if input.is_empty() {
return Err(Error::InvalidData(
"qrcode_native: empty input is not encodable".to_string(),
));
}
let ec_level = match opts.get("eclevel").unwrap_or("M") {
"L" => 0u8,
"M" => 1u8,
"Q" => 2u8,
"H" => 3u8,
other => return Err(Error::InvalidOption(format!("eclevel={other}"))),
};
if let Some(ver_str) = opts.get("version") {
let version: u8 = ver_str
.parse()
.map_err(|_| Error::InvalidOption(format!("version={ver_str}")))?;
if !(1..=40).contains(&version) {
return Err(Error::InvalidOption(format!(
"QR version must be 1..=40, got {version}"
)));
}
let metric_idx = 4 + (version - 1) as usize;
encode_qr_at_metric_with_fnc1(input, metric_idx, ec_level, fnc1first)
} else {
let (version, final_ec) =
auto_select_full_qr_version_with_fnc1(input, ec_level, fnc1first)?;
let metric_idx = 4 + (version - 1) as usize;
encode_qr_at_metric_with_fnc1(input, metric_idx, final_ec, fnc1first)
}
}
pub fn encode_micro_with_options(input: &[u8], opts: &crate::Options) -> Result<BitMatrix, Error> {
if input.is_empty() {
return Err(Error::InvalidData(
"qrcode_native: empty Micro QR input is not encodable".to_string(),
));
}
let ec_level = match opts.get("eclevel").unwrap_or("L") {
"L" => 0u8,
"M" => 1u8,
"Q" => 2u8,
"H" => {
return Err(Error::InvalidOption(
"Micro QR does not support EC level H".to_string(),
));
}
other => return Err(Error::InvalidOption(format!("eclevel={other}"))),
};
if let Some(ver_str) = opts.get("version") {
let stripped = ver_str.strip_prefix('M').unwrap_or(ver_str);
let n: u8 = stripped
.parse()
.map_err(|_| Error::InvalidOption(format!("version={ver_str}")))?;
if !(1..=4).contains(&n) {
return Err(Error::InvalidOption(format!(
"Micro QR version must be M1..=M4, got M{n}"
)));
}
encode_micro_qr(input, (n - 1) as usize, ec_level)
} else {
encode_micro_auto(input, ec_level)
}
}
pub(crate) fn encode_micro_auto(input: &[u8], requested_ec: u8) -> Result<BitMatrix, Error> {
if input.is_empty() {
return Err(Error::InvalidData(
"qrcode_native: empty input is not encodable as Micro QR".to_string(),
));
}
let (micro_idx, ec_level) = auto_select_micro_qr_version(input, requested_ec)?;
encode_micro_qr(input, micro_idx, ec_level)
}
pub(crate) fn auto_select_micro_qr_version(
msg: &[u8],
requested_ec: u8,
) -> Result<(usize, u8), Error> {
if requested_ec >= 4 {
return Err(Error::InvalidData(format!(
"qrcode_native: ec_level {requested_ec} out of range (0..=3 = L/M/Q/H)"
)));
}
for (micro_idx, m) in FULL_METRICS.iter().enumerate().take(4) {
let layout_id = m.layout_id;
let segments = select_segments(msg, layout_id, false);
let bits = match compose_segments(msg, &segments, layout_id, false) {
Ok(b) => b,
Err(_) => continue,
};
let layout = match block_layout(micro_idx, requested_ec) {
Ok(l) => l,
Err(_) => continue,
};
let lc4b = layout_id == 3 || layout_id == 5;
let dmod = if lc4b {
layout.dcws * 8 - 4
} else {
layout.dcws * 8
};
if (bits.len() as u32) <= dmod {
let mut final_ec = requested_ec;
for try_ec in (requested_ec + 1)..=3 {
if let Ok(layout_upgraded) = block_layout(micro_idx, try_ec) {
let upgraded_dmod = if lc4b {
layout_upgraded.dcws * 8 - 4
} else {
layout_upgraded.dcws * 8
};
if (bits.len() as u32) <= upgraded_dmod {
final_ec = try_ec;
}
}
}
return Ok((micro_idx, final_ec));
}
}
Err(Error::InvalidData(format!(
"qrcode_native: message of {} bytes does not fit any Micro QR \
version (M1..M4) at EC level {requested_ec}",
msg.len()
)))
}
pub(crate) fn auto_select_full_qr_version(msg: &[u8], requested_ec: u8) -> Result<(u8, u8), Error> {
auto_select_full_qr_version_with_fnc1(msg, requested_ec, false)
}
pub(crate) fn auto_select_full_qr_version_with_fnc1(
msg: &[u8],
requested_ec: u8,
fnc1first: bool,
) -> Result<(u8, u8), Error> {
if requested_ec >= 4 {
return Err(Error::InvalidData(format!(
"qrcode_native: ec_level {requested_ec} out of range (0..=3 = L/M/Q/H)"
)));
}
for version in 1..=40u8 {
let metric_idx = 4 + (version - 1) as usize;
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let segments = select_segments(msg, layout_id, fnc1first);
let bits = match compose_segments(msg, &segments, layout_id, fnc1first) {
Ok(b) => b,
Err(_) => continue,
};
let layout = match block_layout(metric_idx, requested_ec) {
Ok(l) => l,
Err(_) => continue,
};
let dmod = layout.dcws * 8;
if (bits.len() as u32) <= dmod {
let final_ec = if fnc1first {
requested_ec
} else {
let mut e = requested_ec;
for try_ec in (requested_ec + 1)..=3 {
if let Ok(layout_upgraded) = block_layout(metric_idx, try_ec) {
let upgraded_dmod = layout_upgraded.dcws * 8;
if (bits.len() as u32) <= upgraded_dmod {
e = try_ec;
}
}
}
e
};
return Ok((version, final_ec));
}
}
Err(Error::InvalidData(format!(
"qrcode_native: message of {} bytes does not fit any Full QR version \
(V1..V40) at EC level {requested_ec}",
msg.len()
)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_rejects_empty() {
let err = encode(b"").unwrap_err();
let Error::InvalidData(msg) = err else {
panic!("encode(b\"\") must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("qrcode_native:"),
"diagnostic must carry the symbology tag; got {msg:?}"
);
assert!(
msg.contains("empty input"),
"diagnostic must call out 'empty input'; got {msg:?}"
);
assert!(
msg.contains("not encodable"),
"diagnostic must use the 'not encodable' predicate; got {msg:?}"
);
}
#[test]
fn encode_full_qr_v1_m_hello() {
let matrix = encode_full_qr(b"HELLO", 1, 1).unwrap();
assert_eq!(matrix.width(), 21);
assert_eq!(matrix.height(), 21);
assert!(matrix.get(0, 0), "TL finder corner (0, 0)");
assert!(matrix.get(8, 13), "V1 dark module at (col 8, row 13)");
let mut dark_count: usize = 0;
for r in 0..21 {
for c in 0..21 {
if matrix.get(c, r) {
dark_count += 1;
}
}
}
assert!(
(100..=350).contains(&dark_count),
"V1-M HELLO should have a roughly-balanced dark count, got {dark_count}"
);
}
#[test]
fn encode_full_qr_v1_m_iso_annex_i() {
let matrix = encode_full_qr(b"01234567", 1, 1).unwrap();
assert_eq!(matrix.width(), 21);
assert_eq!(matrix.height(), 21);
assert!(
matrix.get(8, 13),
"V1 dark module at (row 13, col 8) must be set"
);
}
#[test]
fn encode_full_qr_rejects_invalid_version() {
match encode_full_qr(b"HELLO", 0, 1).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"below-min arm missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("Full QR version 0"),
"below-min arm missing `Full QR version 0` echo: {msg}"
);
assert!(
msg.contains("out of range (1..=40)"),
"below-min arm missing range hint: {msg}"
);
assert!(
!msg.contains("ec_level"),
"wrong arm — ec_level diagnostic leaked: {msg}"
);
}
other => panic!("version 0 should reject as InvalidData, got {other:?}"),
}
match encode_full_qr(b"HELLO", 41, 1).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"above-max arm missing prefix: {msg}"
);
assert!(
msg.contains("Full QR version 41"),
"above-max arm missing `Full QR version 41` echo: {msg}"
);
assert!(
msg.contains("out of range (1..=40)"),
"above-max arm missing range hint: {msg}"
);
}
other => panic!("version 41 should reject as InvalidData, got {other:?}"),
}
}
#[test]
fn encode_full_qr_rejects_invalid_ec_level() {
match encode_full_qr(b"HELLO", 1, 4).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("ec_level 4"),
"missing `ec_level 4` value echo: {msg}"
);
assert!(
msg.contains("out of range (0..=3"),
"missing range hint `(0..=3`: {msg}"
);
assert!(
msg.contains("L/M/Q/H"),
"missing `L/M/Q/H` level-name hint: {msg}"
);
assert!(
!msg.contains("Full QR version"),
"wrong arm — version diagnostic leaked: {msg}"
);
}
other => panic!("ec_level 4 should reject as InvalidData, got {other:?}"),
}
}
#[test]
fn encode_rmqr_r7x43_m_smoke() {
let result = encode_qr_at_metric(b"HELLO", 44, 1);
match &result {
Ok(m) => {
assert_eq!(m.width(), 43, "rMQR R7x43 width");
assert_eq!(m.height(), 7, "rMQR R7x43 height");
}
Err(e) => panic!("encode_qr_at_metric(R7x43, EC M, HELLO) failed: {e:?}"),
}
}
#[test]
fn encode_rmqr_rejects_ec_level_l() {
let result = encode_qr_at_metric(b"HELLO", 44, 0);
assert!(
matches!(
&result,
Err(Error::InvalidOption(_)) | Err(Error::InvalidData(_))
),
"rMQR + EC L must error, got {result:?}",
);
}
#[test]
fn encode_rmqr_rejects_ec_level_q() {
let result = encode_qr_at_metric(b"HELLO", 44, 2);
assert!(
matches!(
&result,
Err(Error::InvalidOption(_)) | Err(Error::InvalidData(_))
),
"rMQR + EC Q must error, got {result:?}",
);
}
#[test]
fn encode_micro_qr_rejects_out_of_range_micro_idx() {
match encode_micro_qr(b"1", 4, 0) {
Err(Error::InvalidData(msg)) => assert!(
msg.contains("qrcode_native:")
&& msg.contains("micro_idx 4")
&& msg.contains("0..4")
&& msg.contains("M1..M4"),
"micro_idx=4 should pin diagnostic 'qrcode_native:' + 'micro_idx 4' + '0..4 = M1..M4', got: {msg}"
),
other => panic!("micro_idx=4 should reject as InvalidData, got {other:?}"),
}
match encode_micro_qr(b"1", 5, 0) {
Err(Error::InvalidData(msg)) => {
assert!(
msg.contains("micro_idx 5"),
"micro_idx=5 diagnostic must echo the caller's value; got {msg}"
);
assert!(
msg.contains("0..4") && msg.contains("M1..M4"),
"micro_idx=5 diagnostic must carry the 0..4 = M1..M4 range hint; got {msg}"
);
}
other => panic!("micro_idx=5 should reject as InvalidData, got {other:?}"),
}
match encode_micro_qr(b"1", 255, 0) {
Err(Error::InvalidData(msg)) => {
assert!(
msg.contains("micro_idx 255"),
"micro_idx=255 diagnostic must echo the caller's value; got {msg}"
);
assert!(
msg.contains("0..4") && msg.contains("M1..M4"),
"micro_idx=255 diagnostic must carry the 0..4 = M1..M4 range hint; got {msg}"
);
}
other => panic!("micro_idx=255 should reject as InvalidData, got {other:?}"),
}
assert!(
encode_micro_qr(b"1", 3, 0).is_ok(),
"micro_idx=3 (M4) max valid must succeed"
);
assert!(
encode_micro_qr(b"1", 0, 0).is_ok(),
"micro_idx=0 (M1) min valid must succeed"
);
}
#[test]
fn encode_rmqr_rejects_unknown_version_string() {
let r7x43 = encode_rmqr(b"HELLO", "R7x43", 1).expect("R7x43 + EC M should encode");
assert_eq!(r7x43.width(), 43);
assert_eq!(r7x43.height(), 7);
let r17x139 =
encode_rmqr(b"HELLO WORLD", "R17x139", 1).expect("R17x139 + EC M should encode");
assert_eq!(r17x139.width(), 139);
assert_eq!(r17x139.height(), 17);
assert_ne!(
(r7x43.width(), r7x43.height()),
(r17x139.width(), r17x139.height()),
"different versions must produce different sizes"
);
match encode_rmqr(b"HELLO", "R5x99", 1) {
Err(Error::InvalidOption(m)) => assert!(
m.contains("unknown rMQR version") && m.contains("R5x99") && m.contains("R7x43"),
"expected unknown-rMQR diagnostic with offending value + spec hint, got: {m}"
),
other => panic!("R5x99 should reject as unknown rMQR, got {other:?}"),
}
match encode_rmqr(b"HELLO", "", 1) {
Err(Error::InvalidOption(m)) => assert!(
m.contains("unknown rMQR version"),
"empty version should reject as unknown, got: {m}"
),
other => panic!("empty version should reject, got {other:?}"),
}
match encode_rmqr(b"HELLO", "r7x43", 1) {
Err(Error::InvalidOption(m)) => assert!(
m.contains("unknown rMQR version") && m.contains("r7x43"),
"lowercase version should reject (FULL_METRICS uses exact case), got: {m}"
),
other => panic!("r7x43 should reject (case-sensitive), got {other:?}"),
}
}
#[test]
fn encode_full_qr_rejects_overflow() {
let too_long = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; match encode_full_qr(too_long.as_bytes(), 1, 1) {
Err(Error::InvalidData(msg)) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("padded_data length"),
"missing `padded_data length` predicate: {msg}"
);
assert!(
msg.contains("does not match dcws"),
"missing `does not match dcws` predicate: {msg}"
);
assert!(
msg.contains("dcws 16"),
"missing V1-L dcws echo `dcws 16`: {msg}"
);
}
other => panic!("33-alpha V1-L should reject as InvalidData, got {other:?}"),
}
}
#[test]
fn auto_select_v1_hello_world() {
let (version, _ec) = auto_select_full_qr_version(b"HELLO WORLD", 0).unwrap();
assert_eq!(version, 1, "11-char alpha fits V1");
}
#[test]
fn auto_select_v1_ec_upgrades() {
let (version, ec) = auto_select_full_qr_version(b"ABC", 0).unwrap();
assert_eq!(version, 1);
assert_eq!(ec, 3, "short payload should auto-upgrade to H");
}
#[test]
fn auto_select_larger_version() {
let payload = "0123456789".repeat(10); let (version, _ec) = auto_select_full_qr_version(payload.as_bytes(), 0).unwrap();
assert!(version >= 2, "100 digits needs V2+, got V{version}");
assert!(version <= 10, "100 digits should fit V2-V10");
}
#[test]
fn auto_select_rejects_overflow() {
let payload = "A".repeat(8000);
let result = auto_select_full_qr_version(payload.as_bytes(), 3);
let err = result.unwrap_err();
match err {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"overflow diagnostic must carry the symbology tag; got {msg}"
);
assert!(
msg.contains("message of 8000 bytes"),
"overflow diagnostic must echo msg.len() (8000); got {msg}"
);
assert!(
msg.contains("does not fit any Full QR version"),
"overflow diagnostic must carry the predicate; got {msg}"
);
assert!(
msg.contains("(V1..V40)"),
"overflow diagnostic must carry the V1..V40 range hint; got {msg}"
);
assert!(
msg.contains("EC level 3"),
"overflow diagnostic must echo requested_ec (3); got {msg}"
);
assert!(
!msg.contains("ec_level 3 out of range"),
"overflow diagnostic must NOT leak the ec_level-range arm; got {msg}"
);
}
other => panic!("expected InvalidData, got {other:?}"),
}
}
#[test]
fn auto_select_rejects_invalid_ec() {
match auto_select_full_qr_version(b"HELLO", 4).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("ec_level 4"),
"missing `ec_level 4` value echo: {msg}"
);
assert!(
msg.contains("out of range (0..=3"),
"missing range hint: {msg}"
);
assert!(
msg.contains("L/M/Q/H"),
"missing `L/M/Q/H` level-name hint: {msg}"
);
assert!(
!msg.contains("Full QR version"),
"wrong arm — version diagnostic leaked into ec_level arm: {msg}"
);
}
other => panic!("ec_level 4 auto-select should reject as InvalidData, got {other:?}"),
}
}
#[test]
fn auto_select_ec_level_boundary_and_diagnostic() {
let result = auto_select_full_qr_version(b"HELLO", 3);
assert!(
result.is_ok(),
"ec=3 (max valid) must succeed for short payload"
);
match auto_select_full_qr_version(b"HELLO", 4) {
Err(Error::InvalidData(msg)) => assert!(
msg.contains("ec_level 4") && msg.contains("0..=3"),
"expected 'ec_level 4 out of range (0..=3 ...)' diagnostic, got: {msg}"
),
other => panic!("ec=4 should reject with InvalidData, got {other:?}"),
}
match auto_select_full_qr_version(b"HELLO", 5) {
Err(Error::InvalidData(msg)) => {
assert!(
msg.contains("ec_level 5"),
"ec=5 diagnostic must echo requested_ec=5; got {msg}"
);
assert!(
msg.contains("0..=3"),
"ec=5 diagnostic must carry the 0..=3 range hint; got {msg}"
);
}
other => panic!("ec=5 should reject with InvalidData, got {other:?}"),
}
match auto_select_full_qr_version(b"HELLO", 255) {
Err(Error::InvalidData(msg)) => {
assert!(
msg.contains("ec_level 255"),
"ec=255 diagnostic must echo requested_ec=255; got {msg}"
);
assert!(
msg.contains("0..=3"),
"ec=255 diagnostic must carry the 0..=3 range hint; got {msg}"
);
}
other => panic!("ec=255 should reject with InvalidData, got {other:?}"),
}
assert!(
auto_select_full_qr_version(b"HELLO", 0).is_ok(),
"ec=0 (min valid) must succeed"
);
}
#[test]
fn auto_select_fnc1first_skips_ec_upgrade() {
let (version_plain, ec_plain) =
auto_select_full_qr_version_with_fnc1(b"1", 0, false).unwrap();
let (version_fnc1, ec_fnc1) = auto_select_full_qr_version_with_fnc1(b"1", 0, true).unwrap();
assert_eq!(version_plain, 1, "plain path picks V1");
assert_eq!(version_fnc1, 1, "fnc1 path picks V1");
assert!(
ec_plain >= ec_fnc1,
"plain path must upgrade (or at least equal) ec={ec_plain} vs fnc1={ec_fnc1}"
);
assert_eq!(ec_fnc1, 0, "fnc1 path must honor requested ec=0 verbatim");
assert!(
ec_plain > 0,
"plain path with 1-byte payload must upgrade ec=0 to higher level (V1 has spare capacity)"
);
}
#[test]
fn encode_auto_selects_v1() {
let matrix = encode(b"HELLO WORLD").unwrap();
assert_eq!(matrix.width(), 21);
assert_eq!(matrix.height(), 21);
}
#[test]
fn encode_auto_selects_larger() {
let payload = "HELLO WORLD HELLO WORLD HELLO WORLD HELLO WORLD HELLO WORLD";
let matrix = encode(payload.as_bytes()).unwrap();
assert!(
matrix.width() >= 25,
"Wide payload should produce >= V2 25x25"
);
}
#[test]
fn format_variants() {
let _ = Format::Full;
let _ = Format::Micro;
let _ = Format::Rmqr;
assert_ne!(Format::Full, Format::Micro);
assert_ne!(Format::Micro, Format::Rmqr);
assert_ne!(Format::Full, Format::Rmqr);
}
#[test]
fn full_metrics_shape_and_anchors() {
assert_eq!(FULL_METRICS.len(), 76);
let n_micro = FULL_METRICS
.iter()
.filter(|m| m.format == Format::Micro)
.count();
let n_full = FULL_METRICS
.iter()
.filter(|m| m.format == Format::Full)
.count();
let n_rmqr = FULL_METRICS
.iter()
.filter(|m| m.format == Format::Rmqr)
.count();
assert_eq!(n_micro, 4);
assert_eq!(n_full, 40);
assert_eq!(n_rmqr, 32);
let m1 = &FULL_METRICS[0];
assert_eq!(m1.format, Format::Micro);
assert_eq!(m1.version_str, "M1");
assert_eq!(m1.rows, 11);
assert_eq!(m1.cols, 11);
assert_eq!(m1.datacap_bits, 36);
assert_eq!(m1.eclen, [2, 99, 99, 99]);
let v1 = &FULL_METRICS[4];
assert_eq!(v1.format, Format::Full);
assert_eq!(v1.version_str, "1");
assert_eq!(v1.rows, 21);
assert_eq!(v1.cols, 21);
assert_eq!(v1.datacap_bits, 208);
assert_eq!(v1.eclen, [7, 10, 13, 17]);
let v7 = &FULL_METRICS[10];
assert_eq!(v7.version_str, "7");
assert_eq!(v7.fimas, 38, "V7 fimas (version-info offset) should be 38");
assert_eq!(v7.eclen, [40, 72, 108, 130]);
let v40 = &FULL_METRICS[43];
assert_eq!(v40.version_str, "40");
assert_eq!(v40.rows, 177);
assert_eq!(v40.cols, 177);
assert_eq!(v40.datacap_bits, 29648);
assert_eq!(v40.eclen, [750, 1372, 2040, 2430]);
let r0 = &FULL_METRICS[44];
assert_eq!(r0.format, Format::Rmqr);
assert_eq!(r0.version_str, "R7x43");
assert_eq!(r0.rows, 7);
assert_eq!(r0.cols, 43);
assert_eq!(r0.eclen, [99, 7, 99, 10]);
let r_last = &FULL_METRICS[75];
assert_eq!(r_last.version_str, "R17x139");
assert_eq!(r_last.rows, 17);
assert_eq!(r_last.cols, 139);
assert_eq!(r_last.datacap_bits, 1860);
assert_eq!(r_last.eclen, [99, 80, 99, 156]);
}
#[test]
fn na_sentinels() {
assert_eq!(NA, 99);
assert_eq!(NA_I8, 99);
}
#[test]
fn bch15_5_encode_anchors() {
assert_eq!(bch15_5_encode(0b00000), 0x0000);
assert_eq!(bch15_5_encode(0b00001), 0x0537);
assert_eq!(bch15_5_encode(0b00010), 0x0A6E);
assert_eq!(bch15_5_encode(0b01000), 0x23D6);
assert_eq!(bch15_5_encode(0b01001), 0x26E1);
for d in 0u8..32u8 {
let enc = bch15_5_encode(d);
assert_eq!(
(enc >> 10) as u8,
d,
"BCH(15,5) for data={d}: top 5 bits should equal data"
);
assert!(enc <= 0x7FFF, "BCH(15,5) output should be ≤ 15 bits");
}
assert_eq!(
bch15_5_encode(0b01000) ^ FORMAT_INFO_MASK_FULL,
0x77C4,
"L+mask0 post-mask per ISO 18004 Table 12"
);
}
#[test]
fn bch18_6_encode_anchors() {
assert_eq!(bch18_6_encode(7), 0x7C94);
for v in 7u8..=40u8 {
let enc = bch18_6_encode(v);
let recovered = (enc >> 12) as u8;
assert_eq!(
recovered, v,
"BCH(18,6) for v={v}: top 6 bits should equal v"
);
assert!(enc <= 0x3FFFF, "BCH(18,6) output should be ≤ 18 bits");
}
}
#[test]
fn bch_constants() {
assert_eq!(BCH_15_5_POLY, 0x537);
assert_eq!(BCH_18_6_POLY, 0x1F25);
assert_eq!(FORMAT_INFO_MASK_FULL, 0x5412);
assert_eq!(FORMAT_INFO_MASK_MICRO, 0x4445);
}
fn bits_to_string(bits: &[bool]) -> String {
bits.iter().map(|b| if *b { '1' } else { '0' }).collect()
}
#[test]
fn encode_numeric_segment_three_digits() {
let bits = encode_numeric_segment(b"123").unwrap();
assert_eq!(bits.len(), 10);
assert_eq!(bits_to_string(&bits), "0001111011");
}
#[test]
fn encode_numeric_segment_iso_annex_h() {
let bits = encode_numeric_segment(b"01234567").unwrap();
assert_eq!(bits.len(), 27);
assert_eq!(
bits_to_string(&bits),
"000000110001010110011000011",
"ISO 18004 Annex H numeric example"
);
}
#[test]
fn encode_numeric_segment_one_remainder() {
let bits = encode_numeric_segment(b"1234").unwrap();
assert_eq!(bits.len(), 14, "3 digits → 10 bits, +1 digit → 4 bits");
assert_eq!(bits_to_string(&bits), "00011110110100");
}
#[test]
fn encode_numeric_segment_rejects_letter() {
match encode_numeric_segment(b"12A").unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("numeric mode"),
"missing `numeric mode` predicate: {msg}"
);
assert!(
msg.contains("expects ASCII digit"),
"missing `expects ASCII digit` hint: {msg}"
);
assert!(
msg.contains("0x41"),
"missing hex echo `0x41` for 'A': {msg}"
);
assert!(
!msg.contains("alphanumeric mode"),
"wrong mode — alphanumeric diagnostic leaked into numeric: {msg}"
);
}
other => panic!("`12A` numeric should reject as InvalidData, got {other:?}"),
}
}
#[test]
fn encode_alphanumeric_segment_iso_annex_h() {
let bits = encode_alphanumeric_segment(b"AC-42").unwrap();
assert_eq!(bits.len(), 28);
assert_eq!(
bits_to_string(&bits),
"0011100111011100111001000010",
"ISO 18004 Annex H alphanumeric example"
);
}
#[test]
fn alphanumeric_value_table() {
assert_eq!(ALPHANUMERIC_VALUE[b'0' as usize], 0);
assert_eq!(ALPHANUMERIC_VALUE[b'9' as usize], 9);
assert_eq!(ALPHANUMERIC_VALUE[b'A' as usize], 10);
assert_eq!(ALPHANUMERIC_VALUE[b'Z' as usize], 35);
assert_eq!(ALPHANUMERIC_VALUE[b' ' as usize], 36);
assert_eq!(ALPHANUMERIC_VALUE[b'$' as usize], 37);
assert_eq!(ALPHANUMERIC_VALUE[b'%' as usize], 38);
assert_eq!(ALPHANUMERIC_VALUE[b'*' as usize], 39);
assert_eq!(ALPHANUMERIC_VALUE[b'+' as usize], 40);
assert_eq!(ALPHANUMERIC_VALUE[b'-' as usize], 41);
assert_eq!(ALPHANUMERIC_VALUE[b'.' as usize], 42);
assert_eq!(ALPHANUMERIC_VALUE[b'/' as usize], 43);
assert_eq!(ALPHANUMERIC_VALUE[b':' as usize], 44);
assert_eq!(ALPHANUMERIC_VALUE[b'a' as usize], -1);
assert_eq!(ALPHANUMERIC_VALUE[b'@' as usize], -1);
assert_eq!(ALPHANUMERIC_VALUE[b'!' as usize], -1);
}
#[test]
fn encode_alphanumeric_segment_rejects_lowercase() {
match encode_alphanumeric_segment(b"Aa").unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("alphanumeric mode"),
"missing `alphanumeric mode` predicate: {msg}"
);
assert!(
msg.contains("rejects byte"),
"missing `rejects byte` hint: {msg}"
);
assert!(
msg.contains("0x61"),
"missing hex echo `0x61` for 'a': {msg}"
);
assert!(
!msg.contains("expects ASCII digit"),
"wrong mode — numeric-mode hint leaked: {msg}"
);
}
other => panic!("`Aa` alphanumeric should reject as InvalidData, got {other:?}"),
}
}
#[test]
fn encode_byte_segment_hi() {
let bits = encode_byte_segment(b"Hi");
assert_eq!(bits.len(), 16);
assert_eq!(bits_to_string(&bits), "0100100001101001");
}
#[test]
fn encode_kanji_segment_iso_example() {
let bits = encode_kanji_segment(&[0x935F]).unwrap();
assert_eq!(bits.len(), 13);
assert_eq!(bits_to_string(&bits), "0110110011111");
}
#[test]
fn encode_kanji_segment_high_band() {
let bits = encode_kanji_segment(&[0xE4AA]).unwrap();
assert_eq!(bits.len(), 13);
assert_eq!(bits_to_string(&bits), "1101010101010");
}
#[test]
fn encode_kanji_segment_rejects_out_of_range() {
match encode_kanji_segment(&[0x0041]).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"low-band arm missing prefix: {msg}"
);
assert!(
msg.contains("kanji mode"),
"low-band arm missing `kanji mode` predicate: {msg}"
);
assert!(
msg.contains("Shift-JIS 0x0041"),
"low-band arm missing `Shift-JIS 0x0041` echo: {msg}"
);
assert!(
!msg.contains("numeric mode") && !msg.contains("alphanumeric mode"),
"wrong mode — non-kanji diagnostic leaked: {msg}"
);
}
other => panic!("low-band kanji 0x0041 should reject as InvalidData, got {other:?}"),
}
match encode_kanji_segment(&[0xA000]).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"gap-band arm missing prefix: {msg}"
);
assert!(
msg.contains("kanji mode"),
"gap-band arm missing predicate: {msg}"
);
assert!(
msg.contains("Shift-JIS 0xA000"),
"gap-band arm missing `Shift-JIS 0xA000` echo (proves the gap-band kills hardcoded 0x0041): {msg}"
);
}
other => panic!("gap-band kanji 0xA000 should reject as InvalidData, got {other:?}"),
}
}
#[test]
fn encode_eci_segment_lengths() {
assert_eq!(bits_to_string(&encode_eci_segment(7).unwrap()), "00000111");
assert_eq!(bits_to_string(&encode_eci_segment(9).unwrap()), "00001001");
assert_eq!(
bits_to_string(&encode_eci_segment(127).unwrap()),
"01111111"
);
assert_eq!(
bits_to_string(&encode_eci_segment(128).unwrap()),
"1000000010000000"
);
assert_eq!(
bits_to_string(&encode_eci_segment(16383).unwrap()),
"1011111111111111"
);
assert_eq!(
bits_to_string(&encode_eci_segment(16384).unwrap()),
"110000000100000000000000"
);
match encode_eci_segment(1_000_000).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("ECI assignment number"),
"missing `ECI assignment number` predicate: {msg}"
);
assert!(
msg.contains("1000000"),
"missing `1000000` value echo: {msg}"
);
assert!(
msg.contains("6-digit max"),
"missing `6-digit max` cap hint: {msg}"
);
}
other => panic!("ECI 1_000_000 should reject as InvalidData, got {other:?}"),
}
}
#[test]
fn cci_bits_anchors() {
assert_eq!(cci_bits(0, Mode::Numeric), Some(10));
assert_eq!(cci_bits(0, Mode::Alphanumeric), Some(9));
assert_eq!(cci_bits(0, Mode::Byte), Some(8));
assert_eq!(cci_bits(0, Mode::Kanji), Some(8));
assert_eq!(cci_bits(1, Mode::Numeric), Some(12));
assert_eq!(cci_bits(1, Mode::Byte), Some(16));
assert_eq!(cci_bits(2, Mode::Numeric), Some(14));
assert_eq!(cci_bits(3, Mode::Numeric), Some(3));
assert_eq!(cci_bits(3, Mode::Alphanumeric), None);
assert_eq!(cci_bits(3, Mode::Byte), None);
assert_eq!(cci_bits(3, Mode::Kanji), None);
assert_eq!(cci_bits(4, Mode::Numeric), Some(4));
assert_eq!(cci_bits(4, Mode::Alphanumeric), Some(3));
assert_eq!(cci_bits(4, Mode::Byte), None);
assert_eq!(cci_bits(7, Mode::Numeric), Some(4));
assert_eq!(cci_bits(7, Mode::Kanji), Some(2));
assert_eq!(cci_bits(38, Mode::Numeric), Some(9));
assert_eq!(cci_bits(38, Mode::Kanji), Some(7));
assert_eq!(cci_bits(0, Mode::Eci), None);
assert_eq!(cci_bits(99, Mode::Numeric), None);
}
#[test]
fn cci_bits_complete_table_roundtrip() {
let modes = [Mode::Numeric, Mode::Alphanumeric, Mode::Byte, Mode::Kanji];
for (bin, row) in CC_LENS.iter().enumerate() {
for (col, &expected) in row.iter().enumerate() {
let got = cci_bits(bin as u8, modes[col]);
if expected < 0 {
assert_eq!(got, None, "bin={bin} mode={col}");
} else {
assert_eq!(got, Some(expected as u8), "bin={bin} mode={col}");
}
}
}
}
#[test]
fn cci_bits_match_metrics_layout_id() {
assert_eq!(FULL_METRICS[4].layout_id, 0);
assert_eq!(cci_bits(FULL_METRICS[4].layout_id, Mode::Numeric), Some(10));
assert_eq!(FULL_METRICS[13].layout_id, 1);
assert_eq!(
cci_bits(FULL_METRICS[13].layout_id, Mode::Numeric),
Some(12)
);
assert_eq!(FULL_METRICS[0].layout_id, 3);
assert_eq!(cci_bits(FULL_METRICS[0].layout_id, Mode::Numeric), Some(3));
assert_eq!(FULL_METRICS[44].layout_id, 7);
assert_eq!(cci_bits(FULL_METRICS[44].layout_id, Mode::Numeric), Some(4));
}
#[test]
fn push_bits_msb_first() {
let mut out = Vec::new();
push_bits(&mut out, 0b1011_0100, 8);
assert_eq!(bits_to_string(&out), "10110100");
push_bits(&mut out, 0b0001, 4);
assert_eq!(bits_to_string(&out), "101101000001");
}
#[test]
fn bits_to_bytes_round_trip() {
let bits = [false, true, false, false, true, false, false, false];
assert_eq!(bits_to_bytes(&bits), vec![0x48]);
let bits16 = vec![
false, true, false, false, false, false, false, false, false, false, false, true,
false, false, true, false,
];
assert_eq!(bits_to_bytes(&bits16), vec![0x40, 0x12]);
let bits4 = vec![false, true, true, false];
assert_eq!(bits_to_bytes(&bits4), vec![0x60]);
}
#[test]
fn terminator_len_table() {
assert_eq!(TERMINATOR_LEN[0], 4, "Full V1-9");
assert_eq!(TERMINATOR_LEN[1], 4, "Full V10-26");
assert_eq!(TERMINATOR_LEN[2], 4, "Full V27-40");
assert_eq!(TERMINATOR_LEN[3], 3, "M1");
assert_eq!(TERMINATOR_LEN[4], 5, "M2");
assert_eq!(TERMINATOR_LEN[5], 7, "M3");
assert_eq!(TERMINATOR_LEN[6], 9, "M4");
for (i, &len) in TERMINATOR_LEN.iter().enumerate().skip(7) {
assert_eq!(len, 3, "rMQR row {i}");
}
}
#[test]
fn padding_codewords_constants() {
assert_eq!(PADDING_CODEWORDS, [0xEC, 0x11]);
}
#[test]
fn pad_codewords_iso_annex_i() {
let mut bits = Vec::new();
for c in "0001".chars() {
bits.push(c == '1');
}
for c in "0000001000".chars() {
bits.push(c == '1');
}
for c in "000000110001010110011000011".chars() {
bits.push(c == '1');
}
assert_eq!(bits.len(), 41);
let pad = pad_codewords(&bits, 0, 72, 9).unwrap();
assert_eq!(pad.len(), 9);
assert_eq!(
pad,
vec![0x10, 0x20, 0x0C, 0x56, 0x61, 0x80, 0xEC, 0x11, 0xEC]
);
}
#[test]
fn pad_codewords_terminator_truncated() {
let bits = [true; 72];
let pad = pad_codewords(&bits, 0, 72, 9).unwrap();
assert_eq!(pad.len(), 9);
assert_eq!(pad, vec![0xFF; 9]);
}
#[test]
fn pad_codewords_overcap_error() {
let bits = vec![false; 100];
match pad_codewords(&bits, 0, 72, 9).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("bit-stream"),
"missing `bit-stream` predicate: {msg}"
);
assert!(
msg.contains("100 bits"),
"missing `100 bits` actual-length echo: {msg}"
);
assert!(
msg.contains("exceeds capacity"),
"missing `exceeds capacity` predicate: {msg}"
);
assert!(
msg.contains("72 bits"),
"missing `72 bits` capacity echo: {msg}"
);
}
other => panic!("100-bit pad_codewords should reject as InvalidData, got {other:?}"),
}
}
#[test]
fn qr_gf256_exp_log_inverses() {
for i in 1u8..=254 {
let e = QR_GF256_EXP[i as usize];
assert_ne!(e, 0, "EXP[{i}] should be non-zero");
assert_eq!(QR_GF256_LOG[e as usize], i, "LOG[EXP[{i}]] should be {i}");
}
}
#[test]
fn qr_gf256_mul_properties() {
for a in [0u8, 1, 2, 7, 13, 100, 255] {
assert_eq!(qr_gf256_mul(a, 0), 0);
assert_eq!(qr_gf256_mul(0, a), 0);
assert_eq!(qr_gf256_mul(a, 1), a);
assert_eq!(qr_gf256_mul(1, a), a);
}
for a in [2u8, 3, 5, 17, 100, 200] {
for b in [2u8, 3, 5, 17, 100, 200] {
assert_eq!(qr_gf256_mul(a, b), qr_gf256_mul(b, a), "commutativity");
}
}
assert_eq!(qr_gf256_mul(2, 2), 4);
assert_eq!(
qr_gf256_mul(QR_GF256_EXP[7], QR_GF256_EXP[248]),
1,
"log-sum mod 255 wraps to 0 → alpha^0 = 1"
);
}
#[test]
fn qr_rs_gen_coeffs_n10() {
let coeffs = qr_rs_gen_coeffs(10);
assert_eq!(coeffs.len(), 10);
let alpha_exps = [45u8, 32, 94, 64, 70, 118, 61, 46, 67, 251];
for (i, &exp) in alpha_exps.iter().enumerate() {
assert_eq!(
coeffs[i], QR_GF256_EXP[exp as usize],
"coeff[{i}] should be α^{exp}"
);
}
}
#[test]
fn qr_rs_block_ecc_iso_annex_i_v1_m() {
let data = [
0x10, 0x20, 0x0C, 0x56, 0x61, 0x80, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
0xEC, 0x11,
];
let expected_ecc = [0xA5, 0x24, 0xD4, 0xC1, 0xED, 0x36, 0xC7, 0x87, 0x2C, 0x55];
let ecc = qr_rs_block_ecc(&data, 10);
assert_eq!(ecc, expected_ecc, "ISO 18004 Annex I.2 EC codewords");
}
#[test]
fn block_layout_v1_l() {
let l = block_layout(4, 0).unwrap();
assert_eq!(l.ncws, 26);
assert_eq!(l.dcws, 19);
assert_eq!(l.ecpb, 7);
assert_eq!(l.ecb1, 1);
assert_eq!(l.ecb2, 0);
assert_eq!(l.dcpb, 19);
assert_eq!(l.rbit, 0);
assert!(!l.lc4b);
}
#[test]
fn block_layout_v5_q() {
let l = block_layout(8, 2).unwrap();
assert_eq!(l.ncws, 134);
assert_eq!(l.dcws, 62);
assert_eq!(l.ecpb, 18);
assert_eq!(l.ecb1, 2);
assert_eq!(l.ecb2, 2);
assert_eq!(l.dcpb, 15);
assert_eq!(l.rbit, 7, "V5 datacap_bits=1079, 1079%8=7");
assert!(!l.lc4b);
}
#[test]
fn block_layout_m1() {
let l = block_layout(0, 0).unwrap();
assert_eq!(l.ncws, 5, "M1 datacap=36, +1 for lc4b");
assert_eq!(l.dcws, 3);
assert_eq!(l.ecpb, 2);
assert_eq!(l.ecb1, 1);
assert_eq!(l.ecb2, 0);
assert!(l.lc4b);
assert_eq!(l.rbit, 0);
}
#[test]
fn block_layout_r7x43() {
let l = block_layout(44, 1).unwrap();
assert_eq!(l.ncws, 13);
assert_eq!(l.dcws, 6);
assert_eq!(l.ecpb, 7);
match block_layout(44, 0).unwrap_err() {
Error::InvalidData(msg) => {
assert!(msg.contains("qrcode_native:"), "missing prefix: {msg}");
assert!(
msg.contains("R7x43"),
"missing `R7x43` version-name echo: {msg}"
);
assert!(
msg.contains("does not support EC level"),
"missing `does not support EC level` predicate: {msg}"
);
assert!(
msg.contains("EC level L"),
"missing `EC level L` letter echo: {msg}"
);
assert!(
!msg.contains("out of range"),
"wrong arm — ec_level range diagnostic leaked: {msg}"
);
}
other => panic!(
"block_layout(44, 0) (R7x43 EC L) should reject as InvalidData, got {other:?}"
),
}
}
#[test]
fn block_layout_rejects_out_of_range() {
match block_layout(4, 4).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"ec_level overflow arm missing prefix: {msg}"
);
assert!(
msg.contains("ec_level 4"),
"ec_level overflow arm missing `ec_level 4` echo: {msg}"
);
assert!(
msg.contains("out of range (0..=3"),
"ec_level overflow arm missing range hint: {msg}"
);
assert!(
!msg.contains("metric_idx"),
"wrong arm — metric_idx diagnostic leaked: {msg}"
);
}
other => panic!("block_layout(4, 4) (ec_level overflow) should reject as InvalidData, got {other:?}"),
}
match block_layout(999, 0).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"metric_idx overflow arm missing prefix: {msg}"
);
assert!(
msg.contains("metric_idx 999"),
"metric_idx overflow arm missing `metric_idx 999` echo: {msg}"
);
assert!(
msg.contains("out of range"),
"metric_idx overflow arm missing `out of range` predicate: {msg}"
);
assert!(
!msg.contains("ec_level"),
"wrong arm — ec_level diagnostic leaked: {msg}"
);
}
other => panic!("block_layout(999, 0) (metric_idx overflow) should reject as InvalidData, got {other:?}"),
}
}
#[test]
fn split_data_blocks_multi_block() {
let layout = block_layout(8, 2).unwrap();
let data: Vec<u8> = (0..62).collect();
let blocks = split_data_blocks(&data, &layout);
assert_eq!(blocks.len(), 4);
assert_eq!(blocks[0].len(), 15);
assert_eq!(blocks[0], &(0..15).collect::<Vec<u8>>());
assert_eq!(blocks[1].len(), 15);
assert_eq!(blocks[1], &(15..30).collect::<Vec<u8>>());
assert_eq!(blocks[2].len(), 16);
assert_eq!(blocks[2], &(30..46).collect::<Vec<u8>>());
assert_eq!(blocks[3].len(), 16);
assert_eq!(blocks[3], &(46..62).collect::<Vec<u8>>());
}
#[test]
fn interleave_blocks_simple_2_block() {
let d: Vec<u8> = (0..5).collect();
let e: Vec<u8> = (100..103).collect();
let d2: Vec<u8> = (10..15).collect();
let e2: Vec<u8> = (200..203).collect();
let interleaved = interleave_blocks(&[&d, &d2], &[e.clone(), e2.clone()]);
let want = vec![
0, 10, 1, 11, 2, 12, 3, 13, 4, 14, 100, 200, 101, 201, 102, 202,
];
assert_eq!(interleaved, want);
}
#[test]
fn interleave_blocks_mixed_block_sizes() {
let d_small: Vec<u8> = vec![1, 2];
let d_large: Vec<u8> = vec![10, 20, 30];
let e_small: Vec<u8> = vec![100];
let e_large: Vec<u8> = vec![200];
let out = interleave_blocks(&[&d_small, &d_large], &[e_small, e_large]);
let want = vec![1, 10, 2, 20, 30, 100, 200];
assert_eq!(out, want);
}
#[test]
fn build_codeword_stream_v1_l_hello_world_oracle() {
let padded_data: [u8; 19] = [
32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17, 236, 17, 236,
];
let stream = build_codeword_stream(&padded_data, 4, 0).unwrap();
let oracle: [u8; 26] = [
32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17, 236, 17, 236,
209, 239, 196, 207, 78, 195, 109,
];
assert_eq!(stream, oracle, "V1-L 'HELLO WORLD' bwip-js oracle");
}
#[test]
fn build_codeword_stream_v1_m_01234567_oracle() {
let padded_data: [u8; 16] = [
16, 32, 12, 86, 97, 128, 236, 17, 236, 17, 236, 17, 236, 17, 236, 17,
];
let stream = build_codeword_stream(&padded_data, 4, 1).unwrap();
let oracle: [u8; 26] = [
16, 32, 12, 86, 97, 128, 236, 17, 236, 17, 236, 17, 236, 17, 236, 17, 165, 36, 212,
193, 237, 54, 199, 135, 44, 85,
];
assert_eq!(stream, oracle, "V1-M '01234567' bwip-js oracle");
}
#[test]
fn build_codeword_stream_v5_q_multi_block_oracle() {
let padded_data: [u8; 62] = [
33, 27, 11, 120, 209, 114, 220, 77, 68, 218, 194, 222, 52, 92, 183, 19, 81, 54, 176,
183, 141, 23, 45, 196, 212, 52, 0, 236, 17, 236, 17, 236, 17, 236, 17, 236, 17, 236,
17, 236, 17, 236, 17, 236, 17, 236, 17, 236, 17, 236, 17, 236, 17, 236, 17, 236, 17,
236, 17, 236, 17, 236,
];
let stream = build_codeword_stream(&padded_data, 8, 2).unwrap();
assert_eq!(
stream.len(),
135,
"V5-Q stream = ncws(134) + rbit(7) zero codeword"
);
let oracle: [u8; 135] = [
33, 19, 17, 17, 27, 81, 236, 236, 11, 54, 17, 17, 120, 176, 236, 236, 209, 183, 17, 17,
114, 141, 236, 236, 220, 23, 17, 17, 77, 45, 236, 236, 68, 196, 17, 17, 218, 212, 236,
236, 194, 52, 17, 17, 222, 0, 236, 236, 52, 236, 17, 17, 92, 17, 236, 236, 183, 236,
17, 17, 236, 236, 182, 231, 135, 135, 112, 198, 147, 147, 20, 209, 7, 7, 208, 230, 41,
41, 195, 83, 128, 128, 45, 133, 150, 150, 98, 147, 120, 120, 45, 123, 184, 184, 59, 97,
37, 37, 109, 238, 181, 181, 175, 185, 205, 205, 145, 205, 222, 222, 168, 174, 231, 231,
105, 114, 8, 8, 195, 184, 44, 44, 143, 241, 81, 81, 15, 19, 173, 173, 111, 164, 80, 80,
0,
];
assert_eq!(stream, oracle, "V5-Q multi-block bwip-js oracle");
}
#[test]
fn build_codeword_stream_r7x43_oracle() {
let padded_data: [u8; 6] = [40, 61, 160, 236, 17, 236];
let stream = build_codeword_stream(&padded_data, 44, 1).unwrap();
let oracle: [u8; 13] = [40, 61, 160, 236, 17, 236, 23, 232, 137, 120, 33, 163, 40];
assert_eq!(stream, oracle, "R7x43 rMQR oracle (EC M)");
}
#[test]
fn build_codeword_stream_m1_lc4b_oracle() {
let padded_data: [u8; 3] = [163, 218, 208];
let stream = build_codeword_stream(&padded_data, 0, 0).unwrap();
let oracle: [u8; 5] = [163, 218, 214, 236, 112];
assert_eq!(stream, oracle, "M1 '12345' lc4b oracle");
}
#[test]
fn build_codeword_stream_length_mismatch() {
let bad = [0u8; 18]; match build_codeword_stream(&bad, 4, 0).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("padded_data length 18"),
"missing actual-length echo `padded_data length 18`: {msg}"
);
assert!(
msg.contains("does not match dcws"),
"missing predicate: {msg}"
);
assert!(
msg.contains("dcws 19"),
"missing expected-length echo `dcws 19` (V1-L data codewords): {msg}"
);
}
other => panic!(
"18-byte V1-L (expects 19) build_codeword_stream should reject as InvalidData, got {other:?}"
),
}
}
#[test]
fn qrcode_mids_shape_and_anchors() {
assert_eq!(QRCODE_MIDS[0][0], Some("0001"));
assert_eq!(QRCODE_MIDS[0][1], Some("0010"));
assert_eq!(QRCODE_MIDS[0][2], Some("0100"));
assert_eq!(QRCODE_MIDS[0][3], Some("1000"));
assert_eq!(QRCODE_MIDS[0][4], Some("0111"));
assert_eq!(QRCODE_MIDS[1][0], Some("0001"));
assert_eq!(QRCODE_MIDS[2][0], Some("0001"));
assert_eq!(QRCODE_MIDS[3][0], Some(""));
assert_eq!(QRCODE_MIDS[3][1], None);
assert_eq!(QRCODE_MIDS[3][4], None);
assert_eq!(QRCODE_MIDS[4][0], Some("0"));
assert_eq!(QRCODE_MIDS[4][1], Some("1"));
assert_eq!(QRCODE_MIDS[4][2], None);
assert_eq!(QRCODE_MIDS[5][0], Some("00"));
assert_eq!(QRCODE_MIDS[5][3], Some("11"));
assert_eq!(QRCODE_MIDS[5][4], None);
assert_eq!(QRCODE_MIDS[6][0], Some("000"));
assert_eq!(QRCODE_MIDS[6][3], Some("011"));
assert_eq!(QRCODE_MIDS[6][4], None);
assert_eq!(QRCODE_MIDS[7][0], Some("001"));
assert_eq!(QRCODE_MIDS[7][4], Some("111"));
assert_eq!(QRCODE_MIDS[38][0], Some("001"));
}
#[test]
fn mode_threshold_tables_anchors() {
assert_eq!(QRCODE_MODE0_FORCE_KB[0], 1);
assert_eq!(QRCODE_MODE0_FORCE_KB[3], INFEAS);
assert_eq!(QRCODE_MODE0_FORCE_KB[5], 1);
for (i, v) in QRCODE_MODE0_FORCE_N.iter().enumerate() {
assert_eq!(*v, 1, "FORCE_N row {i}");
}
assert_eq!(QRCODE_MODE_BN_BEFORE_E[0], 3);
assert_eq!(QRCODE_MODE_AN_BEFORE_A[38], 11);
assert_eq!(QRCODE_MODE_BK_BEFORE_B[3], INFEAS);
assert_eq!(QRCODE_MODE_BA_BEFORE_K[3], INFEAS);
}
#[test]
fn infeas_sentinel() {
assert_eq!(INFEAS, 10_000);
}
#[test]
fn is_alpha_only_byte_membership() {
for c in b'A'..=b'Z' {
assert!(
is_alpha_only_byte(c),
"letter {:?} should be alpha-only",
c as char
);
}
for &c in b" $%*+-./:" {
assert!(
is_alpha_only_byte(c),
"symbol {:?} should be alpha-only",
c as char
);
}
for c in b'0'..=b'9' {
assert!(
!is_alpha_only_byte(c),
"digit {:?} must NOT be alpha-only (this set excludes digits)",
c as char
);
}
for c in b'a'..=b'z' {
assert!(!is_alpha_only_byte(c), "lowercase {:?} rejected", c as char);
}
assert!(!is_alpha_only_byte(b'@'), "@ (one before A) rejected");
assert!(!is_alpha_only_byte(b'['), "[ (one after Z) rejected");
assert!(!is_alpha_only_byte(b'!'), "! rejected");
assert!(!is_alpha_only_byte(b'#'), "# rejected");
assert!(!is_alpha_only_byte(b'_'), "_ rejected");
assert!(!is_alpha_only_byte(b'?'), "? rejected");
assert!(!is_alpha_only_byte(b';'), "; (one after :) rejected");
assert!(!is_alpha_only_byte(b','), ", (one before -) rejected");
assert!(!is_alpha_only_byte(0));
assert!(!is_alpha_only_byte(0x1F));
assert!(!is_alpha_only_byte(0x80));
assert!(!is_alpha_only_byte(0xFF));
}
#[test]
fn digit_value_pins_digit_arm_and_default_error() {
for d in 0..=9 {
assert_eq!(digit_value(b'0' + d).unwrap(), d);
}
for (b, hex) in [
(b'/', "0x2F"), (b':', "0x3A"), (b'A', "0x41"), (b'a', "0x61"),
(b' ', "0x20"), (0, "0x00"), (255, "0xFF"), ] {
match digit_value(b).unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"byte={b:#x}: missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("numeric mode expects ASCII digit"),
"byte={b:#x}: missing `numeric mode expects ASCII digit` predicate: {msg}"
);
assert!(
msg.contains(hex),
"byte={b:#x}: missing `{hex}` hex value-echo: {msg}"
);
}
other => panic!("byte={b:#x} should reject as InvalidData, got {other:?}"),
}
}
match digit_value(b'X').unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing qrcode_native prefix: {msg}"
);
assert!(
msg.contains("numeric mode"),
"missing `numeric mode` mode-name: {msg}"
);
assert!(
msg.contains("expects ASCII digit"),
"missing predicate: {msg}"
);
assert!(msg.contains("0x58"), "expected hex 0x58 in: {msg}");
}
o => panic!("expected InvalidData, got {o:?}"),
}
}
#[test]
fn alpha_value_per_arm_and_reject() {
assert_eq!(alpha_value(b'0').unwrap(), 0);
assert_eq!(alpha_value(b'9').unwrap(), 9);
assert_eq!(alpha_value(b'A').unwrap(), 10);
assert_eq!(alpha_value(b'Z').unwrap(), 35);
assert_eq!(alpha_value(b' ').unwrap(), 36);
assert_eq!(alpha_value(b'$').unwrap(), 37);
assert_eq!(alpha_value(b'%').unwrap(), 38);
assert_eq!(alpha_value(b'*').unwrap(), 39);
assert_eq!(alpha_value(b'+').unwrap(), 40);
assert_eq!(alpha_value(b'-').unwrap(), 41);
assert_eq!(alpha_value(b'.').unwrap(), 42);
assert_eq!(alpha_value(b'/').unwrap(), 43);
assert_eq!(alpha_value(b':').unwrap(), 44);
for (b, hex) in [
(b'a', "0x61"),
(b'!', "0x21"),
(b'#', "0x23"),
(b'@', "0x40"),
(b'[', "0x5B"),
] {
match alpha_value(b).unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"byte={b:#x}: missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("alphanumeric mode"),
"byte={b:#x}: missing `alphanumeric mode` mode-name: {msg}"
);
assert!(
msg.contains(hex),
"byte={b:#x}: missing `{hex}` hex value-echo: {msg}"
);
}
other => panic!("byte={b:#x} should reject, got {other:?}"),
}
}
}
#[test]
fn is_valid_shift_jis_band_and_low_byte_checks() {
assert!(is_valid_shift_jis(0x81, 0x40), "0x8140 (first band start)");
assert!(is_valid_shift_jis(0x9F, 0xFC), "0x9FFC (first band end)");
assert!(is_valid_shift_jis(0xE0, 0x40), "0xE040 (second band start)");
assert!(is_valid_shift_jis(0xEB, 0xBF), "0xEBBF (second band end)");
assert!(is_valid_shift_jis(0x88, 0x50));
assert!(
!is_valid_shift_jis(0x81, 0x7F),
"lo=0x7F must be excluded even if hi is in range"
);
assert!(!is_valid_shift_jis(0x88, 0x7F));
assert!(
!is_valid_shift_jis(0x81, 0x39),
"lo<0x40 rejected (just below range)"
);
assert!(!is_valid_shift_jis(0x81, 0x00));
assert!(
!is_valid_shift_jis(0x81, 0xFD),
"lo>0xFC rejected (just above range)"
);
assert!(!is_valid_shift_jis(0x81, 0xFF));
assert!(
!is_valid_shift_jis(0xA0, 0x40),
"0xA040 in the gap between the two bands"
);
assert!(!is_valid_shift_jis(0xDF, 0x40), "0xDF40 still in the gap");
assert!(!is_valid_shift_jis(0xEC, 0x40), "0xEC40 beyond second band");
assert!(!is_valid_shift_jis(0x80, 0x40), "0x8040 below first band");
assert!(
!is_valid_shift_jis(0x9F, 0xFD),
"0x9FFD just past first band (also lo>0xFC)"
);
}
#[test]
fn is_kanji_leader_ranges() {
assert!(is_kanji_leader(0x81));
assert!(is_kanji_leader(0x90));
assert!(is_kanji_leader(0x9F));
assert!(is_kanji_leader(0xE0));
assert!(is_kanji_leader(0xEB));
assert!(!is_kanji_leader(0x00));
assert!(!is_kanji_leader(0x80));
assert!(!is_kanji_leader(0xA0));
assert!(!is_kanji_leader(0xDF));
assert!(!is_kanji_leader(0xEC));
assert!(!is_kanji_leader(0xFF));
}
#[test]
fn input_counters_numeric() {
let c = compute_input_counters(b"01234567", true);
assert_eq!(c.num_n.len(), 9, "msglen + 1 entries");
assert_eq!(c.num_n, vec![8, 7, 6, 5, 4, 3, 2, 1, 0]);
assert_eq!(c.num_a, vec![0; 9]);
assert_eq!(c.num_a_or_n, vec![8, 7, 6, 5, 4, 3, 2, 1, 0]);
assert_eq!(c.num_b, vec![0; 9]);
assert_eq!(c.num_k, vec![0; 9]);
assert_eq!(c.next_n, vec![0; 9]);
assert_eq!(c.next_a, vec![8, 7, 6, 5, 4, 3, 2, 1, 0]);
}
#[test]
fn input_counters_alphanumeric() {
let c = compute_input_counters(b"HELLO WORLD", true);
assert_eq!(c.num_n.len(), 12);
assert_eq!(c.num_n, vec![0; 12]);
assert_eq!(c.num_a, vec![11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
assert_eq!(c.num_a_or_n, vec![11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
assert_eq!(c.num_b, vec![0; 12]);
assert_eq!(c.num_k, vec![0; 12]);
assert_eq!(c.next_n[0], 11);
assert_eq!(c.next_a, vec![0; 12]);
}
#[test]
fn input_counters_mixed() {
let c = compute_input_counters(b"ABC123def", true);
assert_eq!(c.num_n.len(), 10);
assert_eq!(c.num_n, vec![0, 0, 0, 3, 2, 1, 0, 0, 0, 0]);
assert_eq!(c.num_a, vec![3, 2, 1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(c.num_a_or_n, vec![6, 5, 4, 3, 2, 1, 0, 0, 0, 0]);
assert_eq!(c.num_b, vec![0, 0, 0, 0, 0, 0, 3, 2, 1, 0]);
assert_eq!(c.num_k, vec![0; 10]);
}
#[test]
fn input_counters_kanji_active() {
let c = compute_input_counters(&[0x93, 0x5F], false);
assert_eq!(c.num_k.len(), 3);
assert_eq!(c.num_k, vec![1, 0, 0]);
assert_eq!(c.next_k[0], 0);
}
#[test]
fn input_counters_kanji_suppressed() {
let c = compute_input_counters(&[0x93, 0x5F], true);
assert_eq!(c.num_k, vec![0, 0, 0]);
assert_eq!(c.num_b, vec![2, 1, 0]);
}
#[test]
fn input_counters_kanji_unpaired() {
let c = compute_input_counters(&[0x93], false);
assert_eq!(c.num_k, vec![0, 0]);
assert_eq!(c.num_b, vec![1, 0]);
}
#[test]
fn select_segments_all_numeric_v1() {
let segs = select_segments(b"01234567", 0, false);
assert_eq!(segs.len(), 1);
assert_eq!(segs[0].mode, Mode::Numeric);
assert_eq!(segs[0].start, 0);
assert_eq!(segs[0].len, 8);
}
#[test]
fn select_segments_all_alphanumeric_v1() {
let segs = select_segments(b"HELLO WORLD", 0, false);
assert_eq!(segs.len(), 1);
assert_eq!(segs[0].mode, Mode::Alphanumeric);
assert_eq!(segs[0].len, 11);
}
#[test]
fn select_segments_byte_only_v1() {
let segs = select_segments(b"hello", 0, false);
assert!(
!segs.is_empty(),
"select_segments(b\"hello\", v=0, kanji=false) (pure lowercase → Byte mode) must produce ≥1 segment; got empty vec"
);
assert_eq!(
segs[0].mode,
Mode::Byte,
"lowercase \"hello\" must select Mode::Byte (not Alphanumeric — lowercase is not in the QR alphanumeric set); got {:?}",
segs[0].mode
);
assert_eq!(
segs[0].len, 5,
"first segment len must equal payload length 5 (no spurious split); got {}",
segs[0].len
);
}
#[test]
fn compose_v1_l_hello_world_matches_oracle() {
let segs = select_segments(b"HELLO WORLD", 0, false);
let bits = compose_segments(b"HELLO WORLD", &segs, 0, false).unwrap();
let data = pad_codewords(&bits, 0, 152, 19).unwrap();
let expected: [u8; 19] = [
32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17, 236, 17, 236,
];
assert_eq!(data, expected, "V1-L 'HELLO WORLD' bwip-js debugcws");
}
#[test]
fn compose_v1_m_01234567_matches_oracle() {
let segs = select_segments(b"01234567", 0, false);
assert_eq!(segs.len(), 1);
assert_eq!(segs[0].mode, Mode::Numeric);
let bits = compose_segments(b"01234567", &segs, 0, false).unwrap();
let data = pad_codewords(&bits, 0, 128, 16).unwrap();
let expected: [u8; 16] = [
16, 32, 12, 86, 97, 128, 236, 17, 236, 17, 236, 17, 236, 17, 236, 17,
];
assert_eq!(
data, expected,
"V1-M '01234567' bwip-js debugcws / ISO Annex I.1"
);
}
#[test]
fn compose_segments_rejects_unsupported_mode() {
let segs = vec![Segment {
mode: Mode::Alphanumeric,
start: 0,
len: 2,
}];
match compose_segments(b"AB", &segs, 3, false).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("Alphanumeric"),
"missing `Alphanumeric` mode echo: {msg}"
);
assert!(
msg.contains("not supported"),
"missing `not supported` predicate: {msg}"
);
assert!(
msg.contains("layout_id 3"),
"missing `layout_id 3` (M1) echo: {msg}"
);
assert!(
!msg.contains("out of range"),
"wrong arm — layout_id range diagnostic leaked: {msg}"
);
}
other => {
panic!("M1 (layout_id=3) Alphanumeric should reject as InvalidData, got {other:?}")
}
}
}
#[test]
fn init_pixs_matrix_shapes() {
let v1 = init_pixs_matrix(21, 21);
assert_eq!(v1.len(), 441);
assert!(v1.iter().all(|&c| c == PIXS_UNSET));
let m1 = init_pixs_matrix(11, 11);
assert_eq!(m1.len(), 121);
let r = init_pixs_matrix(7, 43);
assert_eq!(r.len(), 301);
}
#[test]
fn qmv_index() {
assert_eq!(qmv(0, 0, 21), 0);
assert_eq!(qmv(0, 20, 21), 20);
assert_eq!(qmv(1, 0, 21), 21);
assert_eq!(qmv(6, 6, 21), 6 * 21 + 6);
assert_eq!(qmv(20, 20, 21), 440);
}
#[test]
fn finder_v1_full() {
let mut pixs = init_pixs_matrix(21, 21);
place_finder_patterns(&mut pixs, 0, 21, 21);
let cols = 21usize;
assert_eq!(pixs[qmv(0, 0, cols)], 1);
for (r, c) in [(2, 2), (2, 4), (3, 3), (4, 2), (4, 4)] {
assert_eq!(
pixs[qmv(r, c, cols)],
1,
"TL finder centre at ({r},{c}) should be 1"
);
}
assert_eq!(pixs[qmv(1, 1, cols)], 0);
assert_eq!(pixs[qmv(5, 5, cols)], 0);
assert_eq!(pixs[qmv(0, 14, cols)], 1);
assert_eq!(pixs[qmv(0, 20, cols)], 1);
assert_eq!(pixs[qmv(14, 0, cols)], 1);
assert_eq!(pixs[qmv(20, 0, cols)], 1);
for c in 0..=7 {
assert_eq!(pixs[qmv(7, c, cols)], 0, "TL separator row 7 col {c}");
}
}
#[test]
fn finder_m1_micro() {
let mut pixs = init_pixs_matrix(11, 11);
place_finder_patterns(&mut pixs, 3, 11, 11);
assert_eq!(pixs[qmv(0, 0, 11)], 1);
assert_eq!(pixs[qmv(10, 0, 11)], PIXS_UNSET);
}
#[test]
fn timing_v1_full() {
let mut pixs = init_pixs_matrix(21, 21);
place_timing_patterns(&mut pixs, 0, 21, 21, NA, NA);
for c in 8..=12 {
let expected = ((c + 1) % 2) as i8;
assert_eq!(pixs[qmv(6, c, 21)], expected, "row-6 timing at col {c}");
}
for r in 8..=12 {
let expected = ((r + 1) % 2) as i8;
assert_eq!(pixs[qmv(r, 6, 21)], expected, "col-6 timing at row {r}");
}
}
#[test]
fn timing_m1_micro() {
let mut pixs = init_pixs_matrix(11, 11);
place_timing_patterns(&mut pixs, 3, 11, 11, NA, NA);
assert_eq!(pixs[qmv(0, 8, 11)], 1); assert_eq!(pixs[qmv(0, 9, 11)], 0);
assert_eq!(pixs[qmv(0, 10, 11)], 1);
assert_eq!(pixs[qmv(8, 0, 11)], 1);
assert_eq!(pixs[qmv(9, 0, 11)], 0);
assert_eq!(pixs[qmv(10, 0, 11)], 1);
}
#[test]
fn alignment_pattern_full_shape() {
assert_eq!(ALIGNMENT_PATTERN_FULL[0], [1, 1, 1, 1, 1]);
assert_eq!(ALIGNMENT_PATTERN_FULL[1], [1, 0, 0, 0, 1]);
assert_eq!(ALIGNMENT_PATTERN_FULL[2], [1, 0, 1, 0, 1]);
assert_eq!(ALIGNMENT_PATTERN_FULL[3], [1, 0, 0, 0, 1]);
assert_eq!(ALIGNMENT_PATTERN_FULL[4], [1, 1, 1, 1, 1]);
}
#[test]
fn alignment_pattern_rmqr_shape() {
assert_eq!(ALIGNMENT_PATTERN_RMQR[0], [1, 1, 1, 9, 9]);
assert_eq!(ALIGNMENT_PATTERN_RMQR[1], [1, 0, 1, 9, 9]);
assert_eq!(ALIGNMENT_PATTERN_RMQR[2], [1, 1, 1, 9, 9]);
assert_eq!(ALIGNMENT_PATTERN_RMQR[3], [9, 9, 9, 9, 9]);
assert_eq!(ALIGNMENT_PATTERN_RMQR[4], [9, 9, 9, 9, 9]);
assert_eq!(ALGN_SKIP, 9);
}
#[test]
fn alignment_v1_no_patterns() {
let mut pixs = init_pixs_matrix(21, 21);
place_alignment_patterns(&mut pixs, 0, 21, 21, 98, 99);
assert!(pixs.iter().all(|&c| c == PIXS_UNSET));
}
#[test]
fn alignment_v2_single_central() {
let cols: u16 = 25;
let mut pixs = init_pixs_matrix(25, cols);
place_alignment_patterns(&mut pixs, 0, 25, cols, 18, 99);
assert_eq!(pixs[qmv(18, 18, cols as usize)], 1, "V2 center cell");
assert_eq!(pixs[qmv(16, 16, cols as usize)], 1);
assert_eq!(pixs[qmv(16, 20, cols as usize)], 1);
assert_eq!(pixs[qmv(20, 16, cols as usize)], 1);
assert_eq!(pixs[qmv(20, 20, cols as usize)], 1);
assert_eq!(pixs[qmv(17, 17, cols as usize)], 0);
assert_eq!(pixs[qmv(17, 19, cols as usize)], 0);
assert_eq!(pixs[qmv(19, 17, cols as usize)], 0);
assert_eq!(pixs[qmv(19, 19, cols as usize)], 0);
}
#[test]
fn alignment_v7_grid() {
let cols: u16 = 45;
let mut pixs = init_pixs_matrix(45, cols);
place_alignment_patterns(&mut pixs, 0, 45, cols, 22, 38);
let cu = cols as usize;
assert_eq!(pixs[qmv(6, 22, cu)], 1, "V7 alignment (6,22)");
assert_eq!(pixs[qmv(22, 6, cu)], 1, "V7 alignment (22,6)");
assert_eq!(pixs[qmv(22, 22, cu)], 1, "V7 alignment (22,22)");
}
#[test]
fn put_alignment_pattern_skip_9() {
let cols: u16 = 9;
let mut pixs = init_pixs_matrix(9, cols);
put_alignment_pattern(&mut pixs, 0, 0, cols as usize, &ALIGNMENT_PATTERN_RMQR);
assert_eq!(pixs[qmv(0, 0, cols as usize)], 1);
assert_eq!(pixs[qmv(1, 1, cols as usize)], 0);
assert_eq!(pixs[qmv(2, 2, cols as usize)], 1);
assert_eq!(pixs[qmv(3, 0, cols as usize)], PIXS_UNSET);
assert_eq!(pixs[qmv(0, 3, cols as usize)], PIXS_UNSET);
assert_eq!(pixs[qmv(3, 3, cols as usize)], PIXS_UNSET);
}
#[test]
fn finder_pattern_shape() {
assert_eq!(FINDER_PATTERN[0][0], 1);
assert_eq!(FINDER_PATTERN[0][6], 1);
assert_eq!(FINDER_PATTERN[6][0], 1);
assert_eq!(FINDER_PATTERN[6][6], 1);
for row in FINDER_PATTERN.iter().take(5).skip(2) {
for cell in row.iter().take(5).skip(2) {
assert_eq!(*cell, 1, "centre cell of FINDER_PATTERN");
}
}
assert_eq!(FINDER_PATTERN[1][1], 0);
assert_eq!(FINDER_PATTERN[5][5], 0);
assert_eq!(FINDER_PATTERN[1][5], 0);
}
#[test]
fn place_finder_patterns_rmqr_r11x27() {
let rows = 11u16;
let cols = 27u16;
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, 7, rows, cols);
assert_eq!(pixs[qmv(0, 0, cols as usize)], 1, "TL (0,0)");
assert_eq!(pixs[qmv(0, 6, cols as usize)], 1, "TL (0,6)");
assert_eq!(pixs[qmv(6, 0, cols as usize)], 1, "TL (6,0)");
assert_eq!(pixs[qmv(6, 6, cols as usize)], 1, "TL (6,6)");
assert_eq!(pixs[qmv(3, 3, cols as usize)], 1, "TL center");
assert_eq!(pixs[qmv(1, 1, cols as usize)], 0, "TL inner (1,1)");
assert_eq!(pixs[qmv(0, 7, cols as usize)], 0, "TL sep (0,7)");
assert_eq!(pixs[qmv(7, 7, cols as usize)], 0, "TL sep (7,7)");
assert_eq!(pixs[qmv(7, 0, cols as usize)], 0, "TL sep (7,0)");
let cu = cols as usize;
assert_eq!(pixs[qmv(0, cu - 1, cu)], 1, "TR (0,cols-1)");
assert_eq!(pixs[qmv(0, cu - 2, cu)], 1, "TR (0,cols-2)");
assert_eq!(pixs[qmv(0, cu - 3, cu)], 1, "TR (0,cols-3)");
assert_eq!(pixs[qmv(1, cu - 1, cu)], 1, "TR (1,cols-1)");
assert_eq!(pixs[qmv(1, cu - 2, cu)], 0, "TR (1,cols-2)");
assert_eq!(pixs[qmv(2, cu - 1, cu)], 1, "TR (2,cols-1)");
let ru = rows as usize;
assert_eq!(pixs[qmv(ru - 1, 0, cu)], 1, "BL (rows-1,0)");
assert_eq!(pixs[qmv(ru - 2, 0, cu)], 1, "BL (rows-2,0)");
assert_eq!(pixs[qmv(ru - 3, 0, cu)], 1, "BL (rows-3,0)");
assert_eq!(pixs[qmv(ru - 1, 1, cu)], 1, "BL (rows-1,1)");
assert_eq!(pixs[qmv(ru - 2, 1, cu)], 0, "BL (rows-2,1)");
assert_eq!(pixs[qmv(ru - 1, 2, cu)], 1, "BL (rows-1,2)");
for x in 0..5 {
assert_eq!(
pixs[qmv(ru - 5, cu - 1 - x, cu)],
1,
"BR top edge col {}",
cu - 1 - x
);
assert_eq!(
pixs[qmv(ru - 1, cu - 1 - x, cu)],
1,
"BR bottom edge col {}",
cu - 1 - x
);
}
for y in 0..5 {
assert_eq!(
pixs[qmv(ru - 1 - y, cu - 5, cu)],
1,
"BR left edge row {}",
ru - 1 - y
);
assert_eq!(
pixs[qmv(ru - 1 - y, cu - 1, cu)],
1,
"BR right edge row {}",
ru - 1 - y
);
}
assert_eq!(pixs[qmv(ru - 3, cu - 3, cu)], 1, "BR center");
assert_eq!(pixs[qmv(ru - 4, cu - 4, cu)], 0, "BR inner (ru-4,cu-4)");
assert_eq!(pixs[qmv(ru - 2, cu - 2, cu)], 0, "BR inner (ru-2,cu-2)");
assert_eq!(pixs[qmv(ru - 4, cu - 2, cu)], 0, "BR inner (ru-4,cu-2)");
assert_eq!(pixs[qmv(ru - 2, cu - 4, cu)], 0, "BR inner (ru-2,cu-4)");
}
#[test]
fn place_finder_patterns_rmqr_r7x43() {
let rows = 7u16;
let cols = 43u16;
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, 7, rows, cols);
let cu = cols as usize;
let ru = rows as usize;
assert_eq!(pixs[qmv(0, 0, cu)], 1);
assert_eq!(pixs[qmv(6, 0, cu)], 1);
assert_eq!(pixs[qmv(6, 6, cu)], 1);
for x in 0..5 {
assert_eq!(pixs[qmv(2, cu - 1 - x, cu)], 1, "BR top edge");
assert_eq!(pixs[qmv(ru - 1, cu - 1 - x, cu)], 1, "BR bottom edge");
}
assert_eq!(pixs[qmv(4, cu - 3, cu)], 1, "BR center");
assert_eq!(pixs[qmv(3, cu - 2, cu)], 0, "BR inner (3,cu-2)");
assert_eq!(pixs[qmv(ru - 1, 0, cu)], 1, "BL (6,0)");
assert_eq!(pixs[qmv(ru - 2, 0, cu)], 1, "BL (5,0)");
assert_eq!(pixs[qmv(ru - 3, 0, cu)], 1, "BL (4,0)");
assert_eq!(pixs[qmv(ru - 1, 1, cu)], 1, "BL (6,1)");
assert_eq!(pixs[qmv(ru - 2, 1, cu)], 0, "BL (5,1)");
assert_eq!(pixs[qmv(0, cu - 1, cu)], 1, "TR (0,42)");
assert_eq!(pixs[qmv(0, cu - 2, cu)], 1, "TR (0,41)");
assert_eq!(pixs[qmv(0, cu - 3, cu)], 1, "TR (0,40)");
assert_eq!(pixs[qmv(1, cu - 1, cu)], 1, "TR (1,42)");
assert_eq!(pixs[qmv(1, cu - 2, cu)], 0, "TR (1,41)");
assert_eq!(pixs[qmv(2, cu - 1, cu)], 1, "TR (2,42)");
}
#[test]
fn walk_codeword_positions_v1_no_reservations() {
let pixs = init_pixs_matrix(21, 21);
let positions = walk_codeword_positions(0, 21, 21, &pixs);
assert!(positions.len() >= 220, "got {}", positions.len());
assert!(
positions.len() <= 441,
"walker must visit at most 441 positions (V1 grid: 21x21 = 441 modules); got {}",
positions.len()
);
}
#[test]
fn walk_codeword_positions_skips_reserved() {
let mut pixs = init_pixs_matrix(21, 21);
for r in 0..8 {
for c in 0..8 {
pixs[qmv(r, c, 21)] = 1;
}
}
let positions = walk_codeword_positions(0, 21, 21, &pixs);
for &pos in &positions {
let row = pos / 21;
let col = pos % 21;
assert!(
!(row < 8 && col < 8),
"walker visited reserved TL ({row},{col})"
);
}
}
#[test]
fn walk_codeword_positions_v1_with_finders() {
let mut pixs = init_pixs_matrix(21, 21);
place_finder_patterns(&mut pixs, 0, 21, 21);
place_timing_patterns(&mut pixs, 0, 21, 21, NA, NA);
let positions = walk_codeword_positions(0, 21, 21, &pixs);
assert!(
(230..=245).contains(&positions.len()),
"expected 230-245 cells with finder + timing only, got {}",
positions.len()
);
}
#[test]
fn place_codewords_at_round_trip() {
let positions: Vec<usize> = (0..16).collect();
let mut pixs = vec![PIXS_UNSET; 16];
let cws = vec![0x4D, 0xA0];
place_codewords_at(&mut pixs, &positions, &cws).unwrap();
let expected: [i8; 16] = [
0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, ];
assert_eq!(pixs.as_slice(), &expected);
}
#[test]
fn place_codewords_at_rejects_overflow() {
let positions: Vec<usize> = (0..8).collect();
let mut pixs = vec![PIXS_UNSET; 8];
let cws = vec![0u8; 3];
match place_codewords_at(&mut pixs, &positions, &cws).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("codeword stream (24 bits)"),
"missing `codeword stream (24 bits)` actual-length echo: {msg}"
);
assert!(
msg.contains("exceeds available positions (8)"),
"missing `exceeds available positions (8)` capacity echo: {msg}"
);
assert!(
msg.contains("rbit-padded codeword"),
"missing `rbit-padded codeword` BWIPP-specific hint: {msg}"
);
}
other => panic!(
"24-bit cws / 8-pos place_codewords_at should reject as InvalidData, got {other:?}"
),
}
let mut pixs2 = vec![PIXS_UNSET; 8];
let cws_rbit = vec![0u8; 2];
place_codewords_at(&mut pixs2, &positions, &cws_rbit).unwrap();
}
#[test]
fn rmqr_fmtval_tables_match_bwipp_oracle() {
let golden = include_str!("../../../tests/fixtures/qrcode_native_rmqr_fmtvals.json");
let nums: Vec<u32> = golden
.lines()
.filter_map(|l| {
let s = l.trim_start().trim_end_matches(',').trim_end_matches(']');
s.parse::<u32>().ok()
})
.collect();
assert_eq!(nums.len(), 128, "fixture must contain 128 numbers (2 × 64)");
let (rmqr1, rmqr2) = nums.split_at(64);
for (i, &want) in rmqr1.iter().enumerate() {
let got = rmqr_fmtval1(i as u8);
assert_eq!(got, want, "rmqr_fmtval1({i}) golden mismatch");
}
for (i, &want) in rmqr2.iter().enumerate() {
let got = rmqr_fmtval2(i as u8);
assert_eq!(got, want, "rmqr_fmtval2({i}) golden mismatch");
}
}
#[test]
fn qrcode_ec_indicator_rmqr_matches_bwipp() {
assert_eq!(QRCODE_EC_INDICATOR_RMQR, [-1, 0, -1, 1]);
}
#[test]
fn rmqr_formatfimmap_matches_bwipp_oracle() {
let corpus = include_str!("../../../tests/fixtures/qrcode_native_rmqr_formatfimmap.txt");
let mut tested = 0;
for line in corpus.lines() {
if line.is_empty() || line.starts_with('#') {
continue;
}
let mut parts = line.splitn(4, '\t');
let vstr = parts.next().expect("missing vstr");
let rows: u16 = parts
.next()
.expect("missing rows")
.parse()
.expect("bad rows");
let cols: u16 = parts
.next()
.expect("missing cols")
.parse()
.expect("bad cols");
let pairs_str = parts.next().expect("missing pairs");
let expected_pairs: Vec<((u16, u16), (u16, u16))> = pairs_str
.split(';')
.map(|p| {
let nums: Vec<u16> =
p.split(',').map(|n| n.parse().expect("bad num")).collect();
assert_eq!(nums.len(), 4, "{vstr}: pair must have 4 numbers");
((nums[0], nums[1]), (nums[2], nums[3]))
})
.collect();
assert_eq!(expected_pairs.len(), 18, "{vstr}: 18 pairs expected");
let got = rmqr_formatfimmap_pairs(rows, cols);
for (i, (want, got_pair)) in expected_pairs.iter().zip(got.iter()).enumerate() {
assert_eq!(
got_pair, want,
"{vstr} cluster {i}: got {got_pair:?} want {want:?}"
);
}
tested += 1;
}
assert_eq!(tested, 32, "expected 32 rMQR variants");
}
#[test]
fn format_info_reservation_v1_full() {
let mut pixs = init_pixs_matrix(21, 21);
let count = place_format_info_reservation(&mut pixs, 0, 21, 21);
assert_eq!(count, 31);
let cols_u = 21usize;
assert_eq!(pixs[qmv(0, 8, cols_u)], 1);
assert_eq!(pixs[qmv(5, 8, cols_u)], 1);
assert_eq!(pixs[qmv(7, 8, cols_u)], 1); assert_eq!(pixs[qmv(8, 8, cols_u)], 1); assert_eq!(pixs[qmv(8, 7, cols_u)], 1);
assert_eq!(pixs[qmv(8, 0, cols_u)], 1); assert_eq!(pixs[qmv(8, 20, cols_u)], 1); assert_eq!(pixs[qmv(8, 14, cols_u)], 1); assert_eq!(pixs[qmv(8, 13, cols_u)], 1); assert_eq!(pixs[qmv(13, 8, cols_u)], 0); assert_eq!(pixs[qmv(14, 8, cols_u)], 1);
assert_eq!(pixs[qmv(20, 8, cols_u)], 1);
}
#[test]
fn format_info_reservation_m1_micro() {
let mut pixs = init_pixs_matrix(11, 11);
let count = place_format_info_reservation(&mut pixs, 3, 11, 11);
assert_eq!(count, 15);
let cu = 11usize;
assert_eq!(pixs[qmv(1, 8, cu)], 1);
assert_eq!(pixs[qmv(8, 8, cu)], 1);
assert_eq!(pixs[qmv(8, 1, cu)], 1);
assert_eq!(pixs[qmv(8, 7, cu)], 1);
}
#[test]
fn format_info_reservation_rmqr_basic_count() {
let mut pixs = init_pixs_matrix(7, 43);
let count = place_format_info_reservation(&mut pixs, 7, 7, 43);
assert_eq!(count, 36, "rMQR formatfimmap returns 36 placement attempts");
let cu = 43usize;
assert_eq!(pixs[qmv(3, 11, cu)], 1, "rMQR cluster 0 TL");
assert_eq!(pixs[qmv(1, 40, cu)], 1, "rMQR cluster 0 Dup");
}
#[test]
fn version_info_reservation_v7() {
let mut pixs = init_pixs_matrix(45, 45);
let count = place_version_info_reservation(&mut pixs, 0, 45, 45, 7);
assert_eq!(count, 36, "V7+ version-info has 36 reserved cells");
let cu = 45usize;
for r in 34..=36 {
for c in 0..=5 {
assert_eq!(
pixs[qmv(r, c, cu)],
0,
"V7 BL version-info at ({r},{c}) should be 0"
);
}
}
for r in 0..=5 {
for c in 34..=36 {
assert_eq!(
pixs[qmv(r, c, cu)],
0,
"V7 TR version-info at ({r},{c}) should be 0"
);
}
}
}
#[test]
fn version_info_reservation_pre_v7() {
let mut pixs = init_pixs_matrix(21, 21);
let count = place_version_info_reservation(&mut pixs, 0, 21, 21, 1);
assert_eq!(count, 0);
assert!(pixs.iter().all(|&c| c == PIXS_UNSET));
}
#[test]
fn version_info_reservation_micro_no_op() {
let mut pixs = init_pixs_matrix(11, 11);
let count = place_version_info_reservation(&mut pixs, 3, 11, 11, 7);
assert_eq!(count, 0);
}
#[test]
fn version_info_reservation_v40() {
let mut pixs = init_pixs_matrix(177, 177);
let count = place_version_info_reservation(&mut pixs, 2, 177, 177, 40);
assert_eq!(count, 36);
let cu = 177usize;
assert_eq!(pixs[qmv(166, 0, cu)], 0);
assert_eq!(pixs[qmv(168, 5, cu)], 0);
assert_eq!(pixs[qmv(0, 166, cu)], 0);
assert_eq!(pixs[qmv(5, 168, cu)], 0);
}
#[test]
fn walker_v1_with_full_function_patterns() {
let mut pixs = init_pixs_matrix(21, 21);
place_finder_patterns(&mut pixs, 0, 21, 21);
place_timing_patterns(&mut pixs, 0, 21, 21, NA, NA);
place_format_info_reservation(&mut pixs, 0, 21, 21);
let positions = walk_codeword_positions(0, 21, 21, &pixs);
assert!(
(208..=210).contains(&positions.len()),
"V1 walker should visit close to 208 data cells (ISO V1 = 26 codewords × 8 bits = 208), got {}",
positions.len()
);
}
#[test]
fn mask_fn_0() {
assert!(MASK_FUNCS[0](0, 0));
assert!(!MASK_FUNCS[0](0, 1));
assert!(!MASK_FUNCS[0](1, 0));
assert!(MASK_FUNCS[0](1, 1));
assert!(MASK_FUNCS[0](2, 2));
}
#[test]
fn mask_fn_1() {
for col in 0..5 {
assert!(MASK_FUNCS[1](0, col), "row 0 col {col}");
assert!(!MASK_FUNCS[1](1, col), "row 1 col {col}");
assert!(MASK_FUNCS[1](4, col), "row 4 col {col}");
}
}
#[test]
fn mask_fn_2() {
for row in 0..5 {
assert!(MASK_FUNCS[2](row, 0));
assert!(!MASK_FUNCS[2](row, 1));
assert!(!MASK_FUNCS[2](row, 2));
assert!(MASK_FUNCS[2](row, 3));
assert!(MASK_FUNCS[2](row, 6));
}
}
#[test]
fn mask_fn_3() {
assert!(MASK_FUNCS[3](0, 0));
assert!(MASK_FUNCS[3](1, 2));
assert!(MASK_FUNCS[3](2, 1));
assert!(!MASK_FUNCS[3](1, 1));
}
#[test]
fn mask_fn_4() {
assert!(MASK_FUNCS[4](0, 0));
assert!(MASK_FUNCS[4](1, 1));
assert!(!MASK_FUNCS[4](2, 0));
assert!(!MASK_FUNCS[4](0, 3));
}
#[test]
fn mask_fn_5_6_7() {
assert!(MASK_FUNCS[5](0, 0)); assert!(MASK_FUNCS[5](6, 1)); assert!(!MASK_FUNCS[5](1, 1)); assert!(MASK_FUNCS[6](0, 0));
assert!(MASK_FUNCS[6](1, 1)); assert!(MASK_FUNCS[7](0, 0));
assert!(MASK_FUNCS[7](0, 2)); }
#[test]
fn mask_candidates_per_format() {
assert_eq!(mask_candidates(0), &[0u8, 1, 2, 3, 4, 5, 6, 7]);
assert_eq!(mask_candidates(2), &[0u8, 1, 2, 3, 4, 5, 6, 7]);
assert_eq!(mask_candidates(3), &[1u8, 4, 6, 7]);
assert_eq!(mask_candidates(6), &[1u8, 4, 6, 7]);
assert_eq!(mask_candidates(7), &[4u8]);
assert_eq!(mask_candidates(38), &[4u8]);
}
#[test]
fn apply_mask_to_data_cells_basic() {
let mut pixs = vec![0i8; 9];
apply_mask_to_data_cells(&mut pixs, 0, 3, 3);
assert_eq!(pixs, vec![1, 0, 1, 0, 1, 0, 1, 0, 1]);
}
#[test]
fn evaluate_mask_micro_all_dark() {
let mut pixs = vec![0i8; 121];
let cols = 11usize;
for r in 1..10 {
pixs[qmv(r, 10, cols)] = 1;
}
for c in 1..10 {
pixs[qmv(10, c, cols)] = 1;
}
let score = evaluate_mask_micro(&pixs, 11, 11);
assert_eq!(score, -153);
}
#[test]
fn evaluate_mask_micro_all_light() {
let pixs = vec![0i8; 121];
let score = evaluate_mask_micro(&pixs, 11, 11);
assert_eq!(score, 0);
}
#[test]
fn evaluate_mask_micro_asymmetric() {
let mut pixs = vec![0i8; 121];
let cols = 11usize;
for r in 1..6 {
pixs[qmv(r, 10, cols)] = 1;
}
for c in 1..3 {
pixs[qmv(10, c, cols)] = 1;
}
assert_eq!(evaluate_mask_micro(&pixs, 11, 11), -37);
}
#[test]
fn select_best_micro_mask_rejects_non_micro() {
let pixs = vec![0i8; 441];
let positions: Vec<usize> = (0..208).collect();
match select_best_micro_mask(&pixs, &positions, 0, 21, 21).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"Full-QR layout_id=0 missing prefix: {msg}"
);
assert!(
msg.contains("select_best_micro_mask"),
"missing function name in diagnostic: {msg}"
);
assert!(
msg.contains("non-Micro layout_id"),
"missing `non-Micro layout_id` predicate: {msg}"
);
assert!(
msg.contains("layout_id 0"),
"layout_id=0 value-echo missing: {msg}"
);
}
other => panic!(
"select_best_micro_mask(layout_id=0) should reject as InvalidData, got {other:?}"
),
}
match select_best_micro_mask(&pixs, &positions, 7, 7, 43).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"rMQR layout_id=7 missing prefix: {msg}"
);
assert!(
msg.contains("non-Micro layout_id"),
"missing `non-Micro layout_id` predicate: {msg}"
);
assert!(
msg.contains("layout_id 7"),
"layout_id=7 value-echo missing (proves {{layout_id}} interpolation isn't hardcoded 0): {msg}"
);
}
other => panic!(
"select_best_micro_mask(layout_id=7) should reject as InvalidData, got {other:?}"
),
}
}
#[test]
fn select_best_micro_mask_returns_valid_candidate() {
let pixs = vec![0i8; 121];
let positions: Vec<usize> = (0..36).collect();
let (cand_idx, _result_pixs) =
select_best_micro_mask(&pixs, &positions, 3, 11, 11).unwrap();
assert!(cand_idx < 4, "candidate idx must be 0..=3, got {cand_idx}");
}
#[test]
fn evalfull_n1_simple_runs() {
let rle = vec![0u32, 4, 5, 6, 3, 7];
let (n1, _n3) = evalfull_n1n3(&rle);
assert_eq!(n1, 12);
}
#[test]
fn evalfull_n1_no_long_runs() {
let rle = vec![0u32, 2, 3, 4, 1, 2];
let (n1, n3) = evalfull_n1n3(&rle);
assert_eq!(n1, 0);
assert_eq!(n3, 0);
}
#[test]
fn evalfull_n3_pattern_at_start() {
let rle = vec![0u32, 1, 1, 3, 1, 1, 0];
let (n1, n3) = evalfull_n3_input_to_n3(&rle);
assert_eq!(n1, 0);
assert_eq!(n3, 40, "finder-like pattern at start should add 40");
}
fn evalfull_n3_input_to_n3(rle: &[u32]) -> (u32, u32) {
evalfull_n1n3(rle)
}
#[test]
fn evalfull_n2_all_light() {
let pixs = vec![0i8; 16];
let n2 = evalfull_n2(&pixs, 4, 4);
assert_eq!(n2, 27);
}
#[test]
fn evalfull_n2_checkerboard() {
let mut pixs = vec![0i8; 16];
for r in 0..4 {
for c in 0..4 {
if (r + c) % 2 == 0 {
pixs[r * 4 + c] = 1;
}
}
}
let n2 = evalfull_n2(&pixs, 4, 4);
assert_eq!(n2, 0);
}
#[test]
fn evalfull_n4_balanced() {
let mut pixs = vec![0i8; 16];
for cell in pixs.iter_mut().take(8) {
*cell = 1;
}
let n4 = evalfull_n4(&pixs, 4, 4);
assert_eq!(n4, 0);
}
#[test]
fn evalfull_n4_all_dark() {
let pixs = vec![1i8; 16];
let n4 = evalfull_n4(&pixs, 4, 4);
assert_eq!(n4, 100);
}
#[test]
fn evaluate_mask_full_all_light_v1() {
let pixs = vec![0i8; 441];
let score = evaluate_mask_full(&pixs, 21, 21);
assert_eq!(score, 2098);
}
#[test]
fn select_best_full_mask_rejects_non_full() {
let pixs = vec![0i8; 121];
let positions: Vec<usize> = (0..36).collect();
match select_best_full_mask(&pixs, &positions, 3, 11, 11).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"M1 layout_id=3 missing prefix: {msg}"
);
assert!(
msg.contains("select_best_full_mask"),
"missing function name: {msg}"
);
assert!(
msg.contains("non-Full layout_id"),
"missing `non-Full layout_id` predicate: {msg}"
);
assert!(msg.contains("layout_id 3"), "M1 value-echo missing: {msg}");
}
other => panic!(
"select_best_full_mask(layout_id=3) should reject as InvalidData, got {other:?}"
),
}
match select_best_full_mask(&pixs, &positions, 7, 7, 43).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"R7x43 layout_id=7 missing prefix: {msg}"
);
assert!(
msg.contains("non-Full layout_id"),
"missing `non-Full layout_id` predicate: {msg}"
);
assert!(
msg.contains("layout_id 7"),
"R7x43 value-echo missing (proves {{layout_id}} interpolation isn't hardcoded to 3): {msg}"
);
}
other => panic!(
"select_best_full_mask(layout_id=7) should reject as InvalidData, got {other:?}"
),
}
}
#[test]
fn select_best_full_mask_returns_valid_candidate() {
let pixs = vec![0i8; 441];
let positions: Vec<usize> = (0..208).collect();
let (mask, _pixs) = select_best_full_mask(&pixs, &positions, 0, 21, 21).unwrap();
assert!(mask < 8, "mask must be 0..=7, got {mask}");
}
#[test]
fn ec_indicator_full_constants() {
assert_eq!(QRCODE_EC_INDICATOR_FULL[0], 1, "L");
assert_eq!(QRCODE_EC_INDICATOR_FULL[1], 0, "M");
assert_eq!(QRCODE_EC_INDICATOR_FULL[2], 3, "Q");
assert_eq!(QRCODE_EC_INDICATOR_FULL[3], 2, "H");
}
#[test]
fn format_info_bits_full_l_mask0() {
let result = compute_format_info_bits(0, 0, 0).unwrap();
assert_eq!(
result, 0x77C4,
"Full QR L+mask0 should be 0x77C4 per ISO 18004 Table 12"
);
}
#[test]
fn format_info_bits_full_l_mask1() {
let result = compute_format_info_bits(0, 0, 1).unwrap();
assert_eq!(result, 0x72F3, "ISO 18004 Table 12 L+mask1");
}
#[test]
fn format_info_bits_micro_m1() {
let result = compute_format_info_bits(3, 0, 0).unwrap();
assert_eq!(result, 0x4445, "Micro M1 L+mask_candidate_0");
}
#[test]
fn micro_sym_id_anchors() {
assert_eq!(micro_sym_id(3, 0).unwrap(), 0);
match micro_sym_id(3, 1).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native: Micro QR layout_id 3"),
"M1+M diagnostic must echo layout_id=3; got {msg}"
);
assert!(
msg.contains("doesn't support EC level 1"),
"M1+M diagnostic must echo ec_level=1; got {msg}"
);
}
other => panic!("M1+M: expected InvalidData, got {other:?}"),
}
assert_eq!(micro_sym_id(6, 0).unwrap(), 5);
assert_eq!(micro_sym_id(6, 1).unwrap(), 6);
assert_eq!(micro_sym_id(6, 2).unwrap(), 7);
match micro_sym_id(6, 3).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native: Micro QR layout_id 6"),
"M4+H diagnostic must echo layout_id=6; got {msg}"
);
assert!(
msg.contains("doesn't support EC level 3"),
"M4+H diagnostic must echo ec_level=3; got {msg}"
);
assert!(
!msg.contains("is not a Micro QR layout"),
"M4+H diagnostic must NOT leak the layout-range arm; got {msg}"
);
}
other => panic!("M4+H: expected InvalidData, got {other:?}"),
}
}
#[test]
fn format_info_bits_rmqr_rejected() {
match compute_format_info_bits(7, 1, 4).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing prefix: {msg}"
);
assert!(
msg.contains("Full/Micro layouts only"),
"missing `Full/Micro layouts only` predicate: {msg}"
);
assert!(
msg.contains("rmqr_fmtval"),
"missing rMQR-specific remediation hint: {msg}"
);
}
other => panic!(
"compute_format_info_bits(rMQR layout_id=7) should reject as InvalidData, got {other:?}"
),
}
}
#[test]
fn format_info_bits_rejects_invalid() {
match compute_format_info_bits(0, 4, 0).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"ec_level arm missing prefix: {msg}"
);
assert!(
msg.contains("ec_level 4"),
"ec_level arm missing value echo: {msg}"
);
assert!(
msg.contains("out of range (0..=3"),
"ec_level arm missing range hint: {msg}"
);
assert!(
!msg.contains("mask_index"),
"wrong arm — mask_index diagnostic leaked: {msg}"
);
}
other => panic!(
"compute_format_info_bits(ec_level=4) should reject as InvalidData, got {other:?}"
),
}
match compute_format_info_bits(0, 0, 8).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("Full QR mask_index 8"),
"Full mask arm missing `Full QR mask_index 8` echo: {msg}"
);
assert!(
msg.contains("out of range (0..=7)"),
"Full mask arm missing range hint `0..=7`: {msg}"
);
assert!(
!msg.contains("Micro QR"),
"wrong family — Micro QR diagnostic leaked into Full arm: {msg}"
);
}
other => panic!(
"compute_format_info_bits(Full mask=8) should reject as InvalidData, got {other:?}"
),
}
match compute_format_info_bits(3, 0, 4).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("Micro QR mask_index 4"),
"Micro mask arm missing `Micro QR mask_index 4` echo: {msg}"
);
assert!(
msg.contains("out of range (0..=3)"),
"Micro mask arm missing range hint `0..=3`: {msg}"
);
assert!(
!msg.contains("Full QR mask_index"),
"wrong family — Full QR mask diagnostic leaked into Micro arm: {msg}"
);
}
other => panic!(
"compute_format_info_bits(Micro mask=4) should reject as InvalidData, got {other:?}"
),
}
}
#[test]
fn write_format_info_bits_v1_l_mask0() {
let mut pixs = vec![PIXS_UNSET; 441];
write_format_info_bits(&mut pixs, 0, 21, 21, 0, 0).unwrap();
let cu = 21usize;
let expected = 0x77C4u16;
let pairs = format_info_pairs_full(21, 21);
for (i, &((r1, c1), (r2, c2))) in pairs.iter().enumerate() {
let want = ((expected >> (14 - i)) & 1) as i8;
assert_eq!(
pixs[qmv(r1, c1, cu)],
want,
"TL cluster bit {i} at ({r1},{c1})"
);
assert_eq!(
pixs[qmv(r2, c2, cu)],
want,
"TR/BL cluster bit {i} at ({r2},{c2})"
);
}
assert_eq!(pixs[qmv(13, 8, cu)], 1, "dark module must be 1");
}
#[test]
fn write_format_info_bits_m1() {
let mut pixs = vec![PIXS_UNSET; 121];
write_format_info_bits(&mut pixs, 3, 11, 11, 0, 0).unwrap();
let cu = 11usize;
let expected = 0x4445u16;
for (i, &(r, c)) in FORMAT_INFO_POSITIONS_MICRO.iter().enumerate() {
let want = ((expected >> (14 - i)) & 1) as i8;
assert_eq!(pixs[qmv(r, c, cu)], want, "Micro M1 bit {i} at ({r},{c})");
}
}
#[test]
fn dark_module_write() {
let mut pixs = vec![PIXS_UNSET; 441];
write_dark_module_full(&mut pixs, 21, 21);
assert_eq!(pixs[qmv(13, 8, 21)], 1);
}
#[test]
fn version_info_pairs_v7() {
let pairs = version_info_pairs_full(45);
assert_eq!(pairs[0], ((5, 36), (36, 5)));
assert_eq!(pairs[1], ((5, 35), (35, 5)));
assert_eq!(pairs[2], ((5, 34), (34, 5)));
assert_eq!(pairs[3], ((4, 36), (36, 4)));
assert_eq!(pairs[17], ((0, 34), (34, 0)));
}
#[test]
fn write_version_info_bits_v7() {
let mut pixs = vec![PIXS_UNSET; 45 * 45];
let count = write_version_info_bits(&mut pixs, 0, 45, 45, 7).unwrap();
assert_eq!(count, 36);
let expected = 0x7C94u32;
let cu = 45usize;
let pairs = version_info_pairs_full(45);
for (i, &((r1, c1), (r2, c2))) in pairs.iter().enumerate() {
let want = ((expected >> (17 - i)) & 1) as i8;
assert_eq!(
pixs[qmv(r1, c1, cu)],
want,
"V7 TR cluster {i} at ({r1},{c1})"
);
assert_eq!(
pixs[qmv(r2, c2, cu)],
want,
"V7 BL cluster {i} at ({r2},{c2})"
);
}
}
#[test]
fn write_version_info_bits_pre_v7_no_op() {
let mut pixs = vec![PIXS_UNSET; 21 * 21];
let count = write_version_info_bits(&mut pixs, 0, 21, 21, 1).unwrap();
assert_eq!(count, 0);
assert!(pixs.iter().all(|&c| c == PIXS_UNSET));
}
#[test]
fn write_version_info_bits_non_full_no_op() {
let mut pixs = vec![PIXS_UNSET; 11 * 11];
let count = write_version_info_bits(&mut pixs, 3, 11, 11, 7).unwrap();
assert_eq!(count, 0);
let mut pixs2 = vec![PIXS_UNSET; 7 * 43];
let count2 = write_version_info_bits(&mut pixs2, 7, 7, 43, 7).unwrap();
assert_eq!(count2, 0);
}
#[test]
fn write_version_info_bits_v40() {
let mut pixs = vec![PIXS_UNSET; 177 * 177];
let count = write_version_info_bits(&mut pixs, 2, 177, 177, 40).unwrap();
assert_eq!(count, 36);
let pairs = version_info_pairs_full(177);
assert_eq!(pairs[0], ((5, 168), (168, 5)));
let expected = bch18_6_encode(40);
assert_eq!((expected >> 12) as u8, 40);
}
#[test]
fn write_version_info_bits_rejects_invalid_version() {
let mut pixs = vec![PIXS_UNSET; 21 * 21];
match write_version_info_bits(&mut pixs, 0, 21, 21, 41).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("invalid Full QR version"),
"missing `invalid Full QR version` predicate: {msg}"
);
assert!(
msg.contains("41"),
"missing offending version value `41`: {msg}"
);
assert!(
msg.contains("must be 1..=40"),
"missing `must be 1..=40` range hint: {msg}"
);
assert!(
!msg.contains("out of range (1..=40)"),
"wrong helper — encode_full_qr's `out of range (1..=40)` wording leaked: {msg}"
);
}
other => panic!(
"write_version_info_bits(version=41) should reject as InvalidData, got {other:?}"
),
}
}
#[test]
fn select_best_mask_dispatch() {
let pixs_v1 = vec![0i8; 441];
let positions_v1: Vec<usize> = (0..208).collect();
let (m_full, _) = select_best_mask(&pixs_v1, &positions_v1, 0, 21, 21).unwrap();
assert!(m_full < 8);
let pixs_m1 = vec![0i8; 121];
let positions_m1: Vec<usize> = (0..36).collect();
let (m_micro, _) = select_best_mask(&pixs_m1, &positions_m1, 3, 11, 11).unwrap();
assert!(m_micro < 4, "Micro QR candidate idx must be 0..=3");
let pixs_r = vec![0i8; 301];
let positions_r: Vec<usize> = (0..50).collect();
let (m_rmqr, _) = select_best_mask(&pixs_r, &positions_r, 7, 7, 43).unwrap();
assert_eq!(m_rmqr, 4, "rMQR uses mask 4 with no scoring");
}
#[test]
#[ignore]
fn debug_apply_mask0_v1_l_hello() {
let msg = b"HELLO WORLD";
let metric_idx = 4;
let ec_level = 0;
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
let _ = place_version_info_reservation(&mut pixs, layout_id, rows, cols, 1);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
place_codewords_at(&mut pixs, &positions, &stream).unwrap();
apply_mask_at_positions(&mut pixs, &positions, 0, cols);
write_format_info_bits(&mut pixs, layout_id, rows, cols, ec_level, 0).unwrap();
let cu = cols as usize;
println!("=== V1-L HELLO WORLD forced-mask-0 pixs (vs bwip-js golden) ===");
for r in 0..rows as usize {
let mut line = String::new();
for c in 0..cu {
line.push(match pixs[qmv(r, c, cu)] {
0 => '.',
1 => '#',
-1 => '?',
_ => '!',
});
}
println!("{line}");
}
}
#[test]
#[ignore]
fn debug_walker_full_trace() {
let mut pixs = init_pixs_matrix(21, 21);
place_finder_patterns(&mut pixs, 0, 21, 21);
place_timing_patterns(&mut pixs, 0, 21, 21, NA, NA);
place_alignment_patterns(&mut pixs, 0, 21, 21, 0, 0);
place_format_info_reservation(&mut pixs, 0, 21, 21);
let _ = place_version_info_reservation(&mut pixs, 0, 21, 21, 1);
let positions = walk_codeword_positions(0, 21, 21, &pixs);
for (i, &pos) in positions.iter().enumerate() {
let posy = pos / 21;
let posx = pos % 21;
println!("WALK num={i} posx={posx} posy={posy} pos={pos}");
}
}
#[test]
#[ignore]
fn debug_walker_first_positions() {
let mut pixs = init_pixs_matrix(21, 21);
place_finder_patterns(&mut pixs, 0, 21, 21);
place_timing_patterns(&mut pixs, 0, 21, 21, NA, NA);
place_alignment_patterns(&mut pixs, 0, 21, 21, 0, 0);
place_format_info_reservation(&mut pixs, 0, 21, 21);
let _ = place_version_info_reservation(&mut pixs, 0, 21, 21, 1);
let positions = walk_codeword_positions(0, 21, 21, &pixs);
for (i, &pos) in positions.iter().take(30).enumerate() {
let r = pos / 21;
let c = pos % 21;
println!("walker[{i}] = idx {pos} = (r={r}, c={c})");
}
for (i, &pos) in positions.iter().enumerate().take(35).skip(20) {
let r = pos / 21;
let c = pos % 21;
println!("walker[{i}] = idx {pos} = (r={r}, c={c})");
}
}
#[test]
#[ignore]
fn debug_walker_position_for_cell() {
let msg = b"HELLO WORLD";
let metric_idx = 4;
let ec_level = 0;
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
let _ = place_version_info_reservation(&mut pixs, layout_id, rows, cols, 1);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
println!(
"walker produced {} positions; stream is {} bytes / {} bits",
positions.len(),
stream.len(),
stream.len() * 8
);
let cu = cols as usize;
for &(r, c) in &[
(1usize, 10usize),
(1, 11),
(1, 12),
(1, 9),
(0, 9),
(0, 10),
(20, 20),
(20, 19),
(19, 20),
] {
let idx = qmv(r, c, cu);
if let Some(pos_i) = positions.iter().position(|&p| p == idx) {
let byte = stream[pos_i / 8];
let bit = (byte >> (7 - (pos_i % 8))) & 1;
println!(
"({r},{c}) idx={idx} → walker pos {pos_i} → cws[{}].bit{} = {}",
pos_i / 8,
7 - (pos_i % 8),
bit
);
} else {
println!("({r},{c}) idx={idx} NOT in walker positions");
}
}
}
#[test]
#[ignore]
fn debug_rle_v1_l_mask0() {
let msg = b"HELLO WORLD";
let metric_idx = 4;
let ec_level = 0;
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
let _ = place_version_info_reservation(&mut pixs, layout_id, rows, cols, 1);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
place_codewords_at(&mut pixs, &positions, &stream).unwrap();
apply_mask_at_positions(&mut pixs, &positions, 0, cols);
let cu = cols as usize;
let ru = rows as usize;
for r in 0..ru {
let row_rle = rle_run((0..cu).map(|c| pixs[qmv(r, c, cu)]));
let (n1, n3) = evalfull_n1n3(&row_rle);
println!("ROW {r}: rle={row_rle:?} n1={n1} n3={n3}");
}
for c in 0..cu {
let col_rle = rle_run((0..ru).map(|r| pixs[qmv(r, c, cu)]));
let (n1, n3) = evalfull_n1n3(&col_rle);
println!("COL {c}: rle={col_rle:?} n1={n1} n3={n3}");
}
}
#[test]
#[ignore]
fn debug_n2_blocks_v1_l_mask0() {
let msg = b"HELLO WORLD";
let metric_idx = 4;
let ec_level = 0;
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
let _ = place_version_info_reservation(&mut pixs, layout_id, rows, cols, 1);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
place_codewords_at(&mut pixs, &positions, &stream).unwrap();
apply_mask_at_positions(&mut pixs, &positions, 0, cols);
let cu = cols as usize;
let ru = rows as usize;
let mut count = 0;
for r in 0..(ru - 1) {
for c in 0..(cu - 1) {
let a = pixs[qmv(r, c, cu)];
let b = pixs[qmv(r, c + 1, cu)];
let d = pixs[qmv(r + 1, c, cu)];
let e = pixs[qmv(r + 1, c + 1, cu)];
if a == b && a == d && a == e && (a == 0 || a == 1) {
count += 1;
println!("N2 block at ({r},{c}) val={a}");
}
}
}
println!("total N2 blocks: {count} → N2 penalty {}", count * 3);
}
#[test]
#[ignore]
fn debug_col8_v1_q_hello_mask4() {
let msg = b"HELLO";
let metric_idx = 4;
let ec_level = 2;
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
let _ = place_version_info_reservation(&mut pixs, layout_id, rows, cols, 1);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
place_codewords_at(&mut pixs, &positions, &stream).unwrap();
apply_mask_at_positions(&mut pixs, &positions, 4, cols);
let cu = cols as usize;
let ru = rows as usize;
let mut col8: Vec<i8> = Vec::with_capacity(ru);
for r in 0..ru {
col8.push(pixs[qmv(r, 8, cu)]);
}
println!("Our col 8 (V1-Q HELLO mask 4): {col8:?}");
let col_rle = rle_run((0..ru).map(|r| pixs[qmv(r, 8, cu)]));
println!(" RLE: {col_rle:?}");
}
#[test]
#[ignore]
fn debug_m1_12345_diff() {
let matrix = encode_micro_qr(b"12345", 0, 0).unwrap();
let w = matrix.width();
println!("Our M1 12345 (mask scored):");
for r in 0..w {
let mut line = String::new();
for c in 0..w {
line.push(if matrix.get(c, r) { '#' } else { '.' });
}
println!(" {line}");
}
}
#[test]
#[ignore]
fn debug_m3_hello12_diff() {
let matrix = encode_micro_qr(b"HELLO12", 2, 0).unwrap();
let w = matrix.width();
println!("Our M3 HELLO12 (mask scored):");
for r in 0..w {
let mut line = String::new();
for c in 0..w {
line.push(if matrix.get(c, r) { '#' } else { '.' });
}
println!(" {line}");
}
}
#[test]
#[ignore]
fn debug_m3_compose_bits() {
let msg = b"HELLO12";
let layout_id = 5; let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
println!("M3-L HELLO12: {} bits", bits.len());
let mut s = String::new();
for &b in &bits {
s.push(if b { '1' } else { '0' });
}
println!("bits: {s}");
}
#[test]
#[ignore]
fn debug_m3_cws_stream() {
let msg = b"HELLO12";
let metric_idx = 2;
let ec_level = 0;
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
println!("Our padded data (pre-lc4b): {padded:?}");
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
println!("Our M3-L HELLO12 cws (post-lc4b): {stream:?}");
}
#[test]
#[ignore]
fn debug_m3_walker_trace() {
let msg = b"HELLO12";
let metric_idx = 2; let ec_level = 0;
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
println!(
"walker produced {} positions; stream is {} bytes = {} bits",
positions.len(),
stream.len(),
stream.len() * 8
);
let cu = cols as usize;
for (i, &pos) in positions.iter().enumerate() {
let posy = pos / cu;
let posx = pos % cu;
let byte = stream[i / 8];
let bit = (byte >> (7 - (i % 8))) & 1;
println!("WALK num={i} posx={posx} posy={posy} pos={pos} bit={bit}");
}
}
#[test]
#[ignore]
fn debug_per_line_v1_q_hello_mask4() {
let msg = b"HELLO";
let metric_idx = 4;
let ec_level = 2;
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
let _ = place_version_info_reservation(&mut pixs, layout_id, rows, cols, 1);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
place_codewords_at(&mut pixs, &positions, &stream).unwrap();
apply_mask_at_positions(&mut pixs, &positions, 4, cols);
let cu = cols as usize;
let ru = rows as usize;
for i in 0..cu {
let col_rle = rle_run((0..ru).map(|r| pixs[qmv(r, i, cu)]));
let (n1, n3) = evalfull_n1n3(&col_rle);
println!("COL i={i} m=4 n3_added={n3} n1_added={n1}");
}
for i in 0..ru {
let row_rle = rle_run((0..cu).map(|c| pixs[qmv(i, c, cu)]));
let (n1, n3) = evalfull_n1n3(&row_rle);
println!("ROW i={i} m=4 n3_added={n3} n1_added={n1}");
}
}
#[test]
#[ignore]
fn debug_score_components_v1_q_hello() {
let msg = b"HELLO";
let metric_idx = 4;
let ec_level = 2; let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
let _ = place_version_info_reservation(&mut pixs, layout_id, rows, cols, 1);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
place_codewords_at(&mut pixs, &positions, &stream).unwrap();
for m_idx in 0..8u8 {
let mut trial = pixs.clone();
apply_mask_at_positions(&mut trial, &positions, m_idx, cols);
let cu = cols as usize;
let ru = rows as usize;
let mut n1: u32 = 0;
let mut n3: u32 = 0;
for c in 0..cu {
let col_rle = rle_run((0..ru).map(|r| trial[qmv(r, c, cu)]));
let (n1c, n3c) = evalfull_n1n3(&col_rle);
n1 += n1c;
n3 += n3c;
}
for r in 0..ru {
let row_rle = rle_run((0..cu).map(|c| trial[qmv(r, c, cu)]));
let (n1r, n3r) = evalfull_n1n3(&row_rle);
n1 += n1r;
n3 += n3r;
}
let n2 = evalfull_n2(&trial, rows, cols);
let n4 = evalfull_n4(&trial, rows, cols);
println!(
"MASK_DETAIL m={m_idx} n1={n1} n2={n2} n3={n3} n4={n4} total={}",
n1 + n2 + n3 + n4
);
}
}
#[test]
#[ignore]
fn debug_score_components_v1_l_hello() {
let msg = b"HELLO WORLD";
let metric_idx = 4;
let ec_level = 0;
let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
let _ = place_version_info_reservation(&mut pixs, layout_id, rows, cols, 1);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
place_codewords_at(&mut pixs, &positions, &stream).unwrap();
for m_idx in 0..8u8 {
let mut trial = pixs.clone();
apply_mask_at_positions(&mut trial, &positions, m_idx, cols);
let cu = cols as usize;
let ru = rows as usize;
let mut n1: u32 = 0;
let mut n3: u32 = 0;
for c in 0..cu {
let col_rle = rle_run((0..ru).map(|r| trial[qmv(r, c, cu)]));
let (n1c, n3c) = evalfull_n1n3(&col_rle);
n1 += n1c;
n3 += n3c;
}
for r in 0..ru {
let row_rle = rle_run((0..cu).map(|c| trial[qmv(r, c, cu)]));
let (n1r, n3r) = evalfull_n1n3(&row_rle);
n1 += n1r;
n3 += n3r;
}
let n2 = evalfull_n2(&trial, rows, cols);
let n4 = evalfull_n4(&trial, rows, cols);
println!(
"MASK_DETAIL m={m_idx} n1={n1} n2={n2} n3={n3} n4={n4} total={}",
n1 + n2 + n3 + n4
);
}
}
#[test]
#[ignore]
fn debug_score_each_mask_v1_l_hello() {
let msg = b"HELLO WORLD";
let metric_idx = 4; let ec_level = 0; let m = &FULL_METRICS[metric_idx];
let layout_id = m.layout_id;
let rows = m.rows;
let cols = m.cols;
let datacap_bits = m.datacap_bits;
let layout = block_layout(metric_idx, ec_level).unwrap();
let segments = select_segments(msg, layout_id, false);
let bits = compose_segments(msg, &segments, layout_id, false).unwrap();
let padded = pad_codewords(&bits, layout_id, datacap_bits, layout.dcws).unwrap();
let stream = build_codeword_stream(&padded, metric_idx, ec_level).unwrap();
let mut pixs = init_pixs_matrix(rows, cols);
place_finder_patterns(&mut pixs, layout_id, rows, cols);
place_timing_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_alignment_patterns(&mut pixs, layout_id, rows, cols, m.fimax, m.fimas);
place_format_info_reservation(&mut pixs, layout_id, rows, cols);
let _ = place_version_info_reservation(&mut pixs, layout_id, rows, cols, 1);
let positions = walk_codeword_positions(layout_id, rows, cols, &pixs);
place_codewords_at(&mut pixs, &positions, &stream).unwrap();
for m_idx in 0..8u8 {
let mut trial = pixs.clone();
apply_mask_at_positions(&mut trial, &positions, m_idx, cols);
let score = evaluate_mask_full(&trial, rows, cols);
println!("V1-L HELLO WORLD mask {m_idx} score = {score}");
}
}
#[test]
#[ignore]
fn debug_dump_v1_l_hello() {
let matrix = encode_full_qr(b"HELLO WORLD", 1, 0).unwrap();
let w = matrix.width();
println!("=== qrcode_native: V1-L HELLO WORLD ({}x{}) ===", w, w);
for r in 0..w {
let mut line = String::new();
for c in 0..w {
line.push(if matrix.get(c, r) { '#' } else { '.' });
}
println!("{line}");
}
}
#[test]
#[ignore]
fn debug_dump_finder_only() {
let mut pixs = init_pixs_matrix(21, 21);
place_finder_patterns(&mut pixs, 0, 21, 21);
println!("=== finder-only pixs (V1, 21x21) ===");
for r in 0..21usize {
let mut line = String::new();
for c in 0..21usize {
let v = pixs[qmv(r, c, 21)];
line.push(match v {
-1 => '?',
0 => '.',
1 => '#',
_ => '!',
});
}
println!("{line}");
}
println!(
"TL col-0 stripe: r0..6 = {} {} {} {} {} {} {}",
pixs[qmv(0, 0, 21)],
pixs[qmv(1, 0, 21)],
pixs[qmv(2, 0, 21)],
pixs[qmv(3, 0, 21)],
pixs[qmv(4, 0, 21)],
pixs[qmv(5, 0, 21)],
pixs[qmv(6, 0, 21)],
);
}
#[test]
fn encode_full_qr_pixs_corpus_matches_oracle() {
let corpus = include_str!("../../../tests/fixtures/qrcode_native_pixs.txt");
let mut tested = 0usize;
let mut failures: Vec<String> = Vec::new();
for line in corpus.lines() {
if line.is_empty() || line.starts_with('#') {
continue;
}
let mut parts = line.splitn(6, '\t');
let label = parts.next().expect("missing label");
let text = parts.next().expect("missing text");
let version_str = parts.next().expect("missing version");
let ec_str = parts.next().expect("missing eclevel");
let ec_level: u8 = match ec_str {
"L" => 0,
"M" => 1,
"Q" => 2,
"H" => 3,
other => panic!("bad eclevel {other}"),
};
let width: usize = parts
.next()
.expect("missing width")
.parse()
.expect("bad width");
let want: Vec<u8> = parts
.next()
.expect("missing pixs csv")
.split(',')
.map(|s| s.parse().expect("bad pixs cell"))
.collect();
assert_eq!(want.len(), width * width, "{label}: pixs/width mismatch");
let matrix = if let Some(stripped) = version_str.strip_prefix('M') {
let micro_idx: usize = stripped.parse::<u8>().expect("bad micro idx") as usize - 1;
encode_micro_qr(text.as_bytes(), micro_idx, ec_level)
.unwrap_or_else(|e| panic!("{label}: encode_micro_qr failed: {e:?}"))
} else {
let version: u8 = version_str.parse().expect("bad version");
encode_full_qr(text.as_bytes(), version, ec_level)
.unwrap_or_else(|e| panic!("{label}: encode_full_qr failed: {e:?}"))
};
assert_eq!(matrix.width(), width, "{label}: width mismatch");
assert_eq!(matrix.height(), width, "{label}: height mismatch");
let mut got = vec![0u8; width * width];
for r in 0..width {
for c in 0..width {
if matrix.get(c, r) {
got[r * width + c] = 1;
}
}
}
if got != want {
let diffs: Vec<String> = got
.iter()
.zip(want.iter())
.enumerate()
.filter(|(_, (g, w))| g != w)
.take(8)
.map(|(i, (g, w))| {
let r = i / width;
let c = i % width;
format!("({r},{c}) got={g} want={w}")
})
.collect();
let total_diff = got.iter().zip(want.iter()).filter(|(g, w)| g != w).count();
failures.push(format!(
"{label}: {total_diff} cell(s) differ; first {}: {}",
diffs.len(),
diffs.join(", ")
));
}
tested += 1;
}
assert!(tested >= 9, "expected >= 9 corpus rows, ran {tested}");
assert!(
failures.is_empty(),
"qrcode_native pixs corpus mismatches ({} of {tested}):\n {}",
failures.len(),
failures.join("\n ")
);
}
#[test]
fn encode_micro_qr_pixs_corpus_matches_oracle() {
let corpus = include_str!("../../../tests/fixtures/qrcode_native_micro_pixs.txt");
let mut tested = 0usize;
let mut failures: Vec<String> = Vec::new();
for line in corpus.lines() {
if line.is_empty() || line.starts_with('#') {
continue;
}
let mut parts = line.splitn(6, '\t');
let label = parts.next().expect("missing label");
let text = parts.next().expect("missing text");
let version_str = parts.next().expect("missing version");
let ec_str = parts.next().expect("missing eclevel");
let ec_level: u8 = match ec_str {
"L" => 0,
"M" => 1,
"Q" => 2,
"H" => 3,
other => panic!("bad eclevel {other}"),
};
let width: usize = parts
.next()
.expect("missing width")
.parse()
.expect("bad width");
let want: Vec<u8> = parts
.next()
.expect("missing pixs csv")
.split(',')
.map(|s| s.parse().expect("bad pixs cell"))
.collect();
let stripped = version_str
.strip_prefix('M')
.expect("Micro corpus row must start with 'M'");
let micro_idx: usize = stripped.parse::<u8>().expect("bad micro idx") as usize - 1;
let matrix = encode_micro_qr(text.as_bytes(), micro_idx, ec_level)
.unwrap_or_else(|e| panic!("{label}: encode_micro_qr failed: {e:?}"));
assert_eq!(matrix.width(), width, "{label}: width mismatch");
assert_eq!(matrix.height(), width, "{label}: height mismatch");
let mut got = vec![0u8; width * width];
for r in 0..width {
for c in 0..width {
if matrix.get(c, r) {
got[r * width + c] = 1;
}
}
}
if got != want {
let total = got.iter().zip(want.iter()).filter(|(g, w)| g != w).count();
failures.push(format!("{label}: {total} cell(s) differ"));
}
tested += 1;
}
assert!(tested >= 4, "expected >= 4 micro corpus rows, ran {tested}");
assert!(
failures.is_empty(),
"qrcode_native micro pixs corpus mismatches:\n {}",
failures.join("\n ")
);
}
fn rmqr_version_to_metric_idx(v: &str) -> Option<usize> {
for (idx, m) in FULL_METRICS.iter().enumerate() {
if matches!(m.format, Format::Rmqr) && m.version_str == v {
return Some(idx);
}
}
None
}
#[test]
fn rmqr_walker_positions_r7x43_match_bwipp_oracle() {
let json = include_str!("../../../tests/fixtures/qrcode_native_rmqr_r7x43_walker.json");
let pos_start = json.find("\"positions\":").expect("positions key") + 12;
let pos_end = json[pos_start..]
.find("\"bits\":")
.expect("bits key after positions")
+ pos_start;
let pos_section = &json[pos_start..pos_end];
let mut flat: Vec<i32> = Vec::new();
for line in pos_section.lines() {
let trimmed = line.trim().trim_end_matches(',');
if let Ok(n) = trimmed.parse::<i32>() {
flat.push(n);
}
}
let mut pairs: Vec<(i32, i32)> = Vec::with_capacity(flat.len() / 2);
for chunk in flat.chunks(2) {
if chunk.len() == 2 {
pairs.push((chunk[0], chunk[1]));
}
}
let rows = 7u16;
let cols = 43u16;
let mut pixs = init_pixs_matrix(rows, cols);
place_timing_patterns(&mut pixs, 7, rows, cols, 22, 99);
place_finder_patterns(&mut pixs, 7, rows, cols);
place_alignment_patterns(&mut pixs, 7, rows, cols, 22, 99);
place_format_info_reservation(&mut pixs, 7, rows, cols);
place_version_info_reservation(&mut pixs, 7, rows, cols, 0);
let got_positions = walk_codeword_positions(7, rows, cols, &pixs);
let cu = cols as usize;
let got: Vec<(i32, i32)> = got_positions
.iter()
.map(|&idx| ((idx % cu) as i32, (idx / cu) as i32))
.collect();
for i in 0..pairs.len().min(got.len()).min(10) {
assert_eq!(
got[i], pairs[i],
"rMQR walker pos[{i}] mismatch: got {:?} want {:?}",
got[i], pairs[i]
);
}
let mut diverge_at = None;
let mut gi = 0;
let mut wi = 0;
while gi < got.len() && wi < pairs.len() {
if got[gi] == pairs[wi] {
gi += 1;
wi += 1;
continue;
}
diverge_at = Some((gi, wi, got[gi], pairs[wi]));
break;
}
if let Some((gi, wi, g, w)) = diverge_at {
eprintln!("--- DIVERGENCE at got[{gi}]={g:?} vs want[{wi}]={w:?} ---");
let lo = gi.saturating_sub(3);
let hi = (gi + 5).min(got.len());
for (idx, p) in got.iter().enumerate().take(hi).skip(lo) {
eprintln!(
"got[{idx}]={p:?}{}",
if idx == gi { " <- DIVERGES" } else { "" }
);
}
let lo = wi.saturating_sub(3);
let hi = (wi + 5).min(pairs.len());
for (idx, p) in pairs.iter().enumerate().take(hi).skip(lo) {
eprintln!(
"want[{idx}]={p:?}{}",
if idx == wi { " <- DIVERGES" } else { "" }
);
}
}
assert_eq!(
got.len(),
pairs.len(),
"rMQR walker length mismatch: got {} want {}",
got.len(),
pairs.len()
);
let mut mismatches = 0usize;
for (i, (g, w)) in got.iter().zip(pairs.iter()).enumerate() {
if g != w {
if mismatches < 5 {
eprintln!("pos[{i}]: got {g:?} want {w:?}");
}
mismatches += 1;
}
}
assert_eq!(
mismatches, 0,
"rMQR walker has {mismatches} mismatched positions"
);
}
#[test]
#[ignore]
fn rmqr_diff_r7x43_m_hi() {
let corpus = include_str!("../../../tests/fixtures/qrcode_native_rmqr_pixs.txt");
let line = corpus
.lines()
.find(|l| l.starts_with("r7x43_m_hi\t"))
.expect("missing fixture row");
let mut parts = line.splitn(7, '\t');
let _label = parts.next();
let _text = parts.next();
let _ver = parts.next();
let _ec = parts.next();
let rows: usize = parts.next().unwrap().parse().unwrap();
let cols: usize = parts.next().unwrap().parse().unwrap();
let want: Vec<u8> = parts
.next()
.unwrap()
.split(',')
.map(|s| s.parse().unwrap())
.collect();
let m = encode_qr_at_metric(b"HI", 44, 1).unwrap();
println!("rows={rows} cols={cols}");
println!("--- WANT ---");
for r in 0..rows {
for c in 0..cols {
print!("{}", want[r * cols + c]);
}
println!();
}
println!("--- GOT ---");
for r in 0..rows {
for c in 0..cols {
print!("{}", if m.get(c, r) { 1 } else { 0 });
}
println!();
}
println!("--- DIFF (W=want, G=got, . match) ---");
for r in 0..rows {
for c in 0..cols {
let w = want[r * cols + c];
let g = if m.get(c, r) { 1 } else { 0 };
if w == g {
print!(".");
} else if w == 1 {
print!("W"); } else {
print!("G"); }
}
println!();
}
}
#[test]
fn encode_rmqr_pixs_corpus_matches_oracle() {
let corpus = include_str!("../../../tests/fixtures/qrcode_native_rmqr_pixs.txt");
let mut tested = 0usize;
let mut failures: Vec<String> = Vec::new();
for line in corpus.lines() {
if line.is_empty() || line.starts_with('#') {
continue;
}
let mut parts = line.splitn(7, '\t');
let label = parts.next().expect("missing label");
let text = parts.next().expect("missing text");
let version_str = parts.next().expect("missing version");
let ec_str = parts.next().expect("missing eclevel");
let ec_level: u8 = match ec_str {
"M" => 1,
"H" => 3,
other => panic!("bad rMQR eclevel {other} (only M / H supported)"),
};
let rows: usize = parts
.next()
.expect("missing rows")
.parse()
.expect("bad rows");
let cols: usize = parts
.next()
.expect("missing cols")
.parse()
.expect("bad cols");
let want: Vec<u8> = parts
.next()
.expect("missing pixs csv")
.split(',')
.map(|s| s.parse().expect("bad pixs cell"))
.collect();
let metric_idx = rmqr_version_to_metric_idx(version_str)
.unwrap_or_else(|| panic!("{label}: unknown rMQR version {version_str}"));
let matrix = encode_qr_at_metric(text.as_bytes(), metric_idx, ec_level)
.unwrap_or_else(|e| panic!("{label}: encode_qr_at_metric failed: {e:?}"));
assert_eq!(matrix.width(), cols, "{label}: cols mismatch");
assert_eq!(matrix.height(), rows, "{label}: rows mismatch");
let mut got = vec![0u8; rows * cols];
for r in 0..rows {
for c in 0..cols {
if matrix.get(c, r) {
got[r * cols + c] = 1;
}
}
}
if got != want {
let total = got.iter().zip(want.iter()).filter(|(g, w)| g != w).count();
failures.push(format!("{label}: {total} cell(s) differ"));
}
tested += 1;
}
assert!(tested >= 8, "expected >= 8 rmqr corpus rows, ran {tested}");
assert!(
failures.is_empty(),
"qrcode_native rMQR pixs corpus mismatches:\n {}",
failures.join("\n ")
);
}
#[test]
fn digit_and_alpha_value_boundaries() {
assert_eq!(digit_value(b'0').unwrap(), 0);
assert_eq!(digit_value(b'5').unwrap(), 5);
assert_eq!(digit_value(b'9').unwrap(), 9);
for (b, hex) in [
(b'/', "0x2F"), (b':', "0x3A"), (b'A', "0x41"),
(0, "0x00"),
(255, "0xFF"),
] {
match digit_value(b).unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"byte={b:#x}: missing prefix: {msg}"
);
assert!(
msg.contains("numeric mode expects ASCII digit"),
"byte={b:#x}: missing predicate: {msg}"
);
assert!(
msg.contains(hex),
"byte={b:#x}: missing {hex} hex echo: {msg}"
);
}
other => panic!("byte={b:#x} should reject, got {other:?}"),
}
}
assert_eq!(alpha_value(b'0').unwrap(), 0);
assert_eq!(alpha_value(b'9').unwrap(), 9);
assert_eq!(alpha_value(b'A').unwrap(), 10);
assert_eq!(alpha_value(b'Z').unwrap(), 35);
assert_eq!(alpha_value(b' ').unwrap(), 36);
assert_eq!(alpha_value(b'$').unwrap(), 37);
assert_eq!(alpha_value(b':').unwrap(), 44);
for (b, hex) in [
(b'a', "0x61"), (b'z', "0x7A"),
(b'!', "0x21"),
(b'@', "0x40"),
(b';', "0x3B"), ] {
match alpha_value(b).unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"byte={b:#x}: missing prefix: {msg}"
);
assert!(
msg.contains("alphanumeric mode"),
"byte={b:#x}: missing `alphanumeric mode`: {msg}"
);
assert!(
msg.contains(hex),
"byte={b:#x}: missing {hex} hex echo: {msg}"
);
}
other => panic!("byte={b:#x} should reject, got {other:?}"),
}
}
match digit_value(b'A').unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native: numeric mode"),
"digit_value: prefix + predicate, got {msg}"
);
assert!(
msg.contains("0x41"),
"digit_value: byte echo missing, got {msg}"
);
assert!(
!msg.contains("alphanumeric mode rejects"),
"digit_value must NOT echo the alpha predicate, got {msg}"
);
}
other => panic!("digit_value(b'A'): expected InvalidData, got {other:?}"),
}
match alpha_value(b'a').unwrap_err() {
crate::error::Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native: alphanumeric mode rejects byte"),
"alpha_value: prefix + predicate, got {msg}"
);
assert!(
msg.contains("0x61"),
"alpha_value: byte echo missing, got {msg}"
);
assert!(
!msg.contains("numeric mode expects"),
"alpha_value must NOT echo the digit predicate, got {msg}"
);
}
other => panic!("alpha_value(b'a'): expected InvalidData, got {other:?}"),
}
}
#[test]
fn push_bits_msb_first_qrcode_native() {
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 0, 4);
assert_eq!(out, vec![false; 4]);
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 0xF, 4);
assert_eq!(out, vec![true; 4]);
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 0b1010, 4);
assert_eq!(out, vec![true, false, true, false]);
let mut out: Vec<bool> = Vec::new();
push_bits(&mut out, 0x80, 8);
assert!(out[0]);
assert_eq!(out[1..], vec![false; 7]);
}
#[test]
fn pixs_set_writes_in_bounds_and_silently_drops_oob() {
let mut pixs = vec![-1i8; 9];
pixs_set(&mut pixs, 1, 2, 3, 7);
assert_eq!(pixs[5], 7, "in-bounds write");
assert_eq!(
pixs.iter().filter(|&&v| v != -1).count(),
1,
"only the targeted cell mutated"
);
pixs_set(&mut pixs, 0, 0, 3, 9);
assert_eq!(pixs[0], 9);
pixs_set(&mut pixs, 2, 2, 3, 8);
assert_eq!(pixs[8], 8);
let snapshot = pixs.clone();
pixs_set(&mut pixs, 0, 3, 3, 100);
assert_eq!(
pixs, snapshot,
"col=cols must no-op (kills `col >= cols` → `col > cols`)"
);
pixs_set(&mut pixs, 0, 99, 3, 100);
assert_eq!(pixs, snapshot, "col >> cols must no-op");
pixs_set(&mut pixs, 4, 0, 3, 100);
assert_eq!(pixs, snapshot, "row past last must no-op");
pixs_set(&mut pixs, 3, 0, 3, 100);
assert_eq!(pixs, snapshot, "idx == len must no-op (kills < → <=)");
let mut p2 = vec![-1i8; 4];
pixs_set(&mut p2, 0, 0, 2, 0);
pixs_set(&mut p2, 0, 1, 2, 1);
pixs_set(&mut p2, 1, 0, 2, -1);
pixs_set(&mut p2, 1, 1, 2, 127);
assert_eq!(p2, vec![0, 1, -1, 127]);
}
#[test]
fn rle_run_strict_color_predicate_and_trailing_flush() {
assert_eq!(rle_run(std::iter::empty()), vec![0]);
assert_eq!(rle_run(vec![0_i8].into_iter()), vec![1]);
assert_eq!(rle_run(vec![1_i8].into_iter()), vec![0, 1]);
assert_eq!(
rle_run(vec![0_i8, 0, 1, 1, 1, 0].into_iter()),
vec![2, 3, 1]
);
assert_eq!(rle_run(vec![-1_i8].into_iter()), vec![1]);
assert_eq!(rle_run(vec![2_i8].into_iter()), vec![1]);
assert_eq!(rle_run(vec![1_i8, 1, 1].into_iter()), vec![0, 3]);
assert_eq!(
rle_run(vec![0_i8, 1, 0, 1, 0].into_iter()),
vec![1, 1, 1, 1, 1]
);
assert_eq!(rle_run(vec![-1_i8, 1, -1].into_iter()), vec![1, 1, 1]);
let cells = vec![0_i8, 0, 0, 1, 1, 0, 0, 1, 0, 0];
let runs = rle_run(cells.iter().copied());
let total: u32 = runs.iter().sum();
assert_eq!(
total,
cells.len() as u32,
"sum of run lengths must equal cell count"
);
assert_eq!(runs, vec![3, 2, 2, 1, 2]);
}
#[test]
fn micro_sym_id_layout_ec_level_table() {
assert_eq!(micro_sym_id(3, 0).unwrap(), 0, "M1, ec=0 → 0");
assert_eq!(micro_sym_id(4, 0).unwrap(), 1, "M2, ec=0 → 1");
assert_eq!(micro_sym_id(4, 1).unwrap(), 2, "M2, ec=1 → 2");
assert_eq!(micro_sym_id(5, 0).unwrap(), 3, "M3, ec=0 → 3");
assert_eq!(micro_sym_id(5, 1).unwrap(), 4, "M3, ec=1 → 4");
assert_eq!(micro_sym_id(6, 0).unwrap(), 5, "M4, ec=0 → 5");
assert_eq!(micro_sym_id(6, 1).unwrap(), 6, "M4, ec=1 → 6");
assert_eq!(micro_sym_id(6, 2).unwrap(), 7, "M4, ec=2 → 7");
match micro_sym_id(2, 0).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native: layout_id 2 is not a Micro QR layout"),
"layout_id=2 (under): diagnostic + value echo; got {msg}"
);
assert!(
!msg.contains("doesn't support EC level"),
"layout-range arm must NOT leak None-cell wording; got {msg}"
);
}
other => panic!("layout_id 0 must be rejected (not Micro), got {other:?}"),
}
match micro_sym_id(0, 0).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"layout_id=0: missing `qrcode_native:` prefix: {msg}"
);
assert!(
msg.contains("layout_id 0"),
"layout_id=0: missing `layout_id 0` value echo (kills `{{layout_id}}` interpolation drop): {msg}"
);
assert!(
msg.contains("is not a Micro QR layout"),
"layout_id=0: missing `is not a Micro QR layout` predicate: {msg}"
);
}
other => panic!("layout_id 0 must be rejected (not Micro), got {other:?}"),
}
match micro_sym_id(7, 0).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native: layout_id 7 is not a Micro QR layout"),
"layout_id=7 (over): diagnostic + value echo; got {msg}"
);
}
other => {
panic!("layout_id 7 must be rejected (kills `mi >= 4` → `mi > 4`), got {other:?}")
}
}
match micro_sym_id(255, 0).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native:"),
"layout_id=255: missing prefix: {msg}"
);
assert!(
msg.contains("layout_id 255"),
"layout_id=255: missing value echo: {msg}"
);
assert!(
msg.contains("is not a Micro QR layout"),
"layout_id=255: missing predicate: {msg}"
);
}
other => panic!("layout_id 255 must be rejected, got {other:?}"),
}
match micro_sym_id(3, 1).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("layout_id 3"),
"M1+ec=1: missing layout_id echo: {msg}"
);
assert!(
msg.contains("doesn't support EC level 1"),
"M1+ec=1: missing ec_level echo: {msg}"
);
}
other => panic!("M1+ec=1 must Err, got {other:?}"),
}
match micro_sym_id(3, 2).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("layout_id 3"),
"M1+ec=2: missing layout_id echo: {msg}"
);
assert!(
msg.contains("doesn't support EC level 2"),
"M1+ec=2: missing ec_level echo: {msg}"
);
}
other => panic!("M1+ec=2 must Err, got {other:?}"),
}
match micro_sym_id(4, 2).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("layout_id 4"),
"M2+ec=2: missing layout_id echo: {msg}"
);
assert!(
msg.contains("doesn't support EC level 2"),
"M2+ec=2: missing ec_level echo: {msg}"
);
}
other => panic!("M2+ec=2 must Err, got {other:?}"),
}
match micro_sym_id(5, 2).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("layout_id 5"),
"M3+ec=2: missing layout_id echo: {msg}"
);
assert!(
msg.contains("doesn't support EC level 2"),
"M3+ec=2: missing ec_level echo: {msg}"
);
}
other => panic!("M3+ec=2 must Err, got {other:?}"),
}
match micro_sym_id(6, 3).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("layout_id 6"),
"M4+ec=3: missing layout_id echo: {msg}"
);
assert!(
msg.contains("doesn't support EC level 3"),
"M4+ec=3: missing ec_level echo: {msg}"
);
}
other => panic!("M4+ec=3 must Err, got {other:?}"),
}
match micro_sym_id(3, 4).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native: Micro QR layout_id 3"),
"ec=4 row-overflow (M1): must echo layout_id=3; got {msg}"
);
assert!(
msg.contains("doesn't support EC level 4"),
"ec=4 row-overflow (M1): must echo ec_level=4; got {msg}"
);
assert!(
!msg.contains("is not a Micro QR layout"),
"ec=4 row-overflow must NOT leak layout-range arm; got {msg}"
);
}
other => {
panic!("ec=4 past row length must Err (kills `>= row.len()` → `>`), got {other:?}")
}
}
match micro_sym_id(6, 4).unwrap_err() {
Error::InvalidData(msg) => {
assert!(
msg.contains("qrcode_native: Micro QR layout_id 6"),
"ec=4 row-overflow (M4): must echo layout_id=6 (not 3); got {msg}"
);
assert!(
msg.contains("doesn't support EC level 4"),
"ec=4 row-overflow (M4): must echo ec_level=4; got {msg}"
);
}
other => panic!("ec=4 past row length, got {other:?}"),
}
}
#[test]
fn is_valid_shift_jis_two_range_and_low_byte_mask() {
assert!(
is_valid_shift_jis(0x81, 0x40),
"lower-range start, low-byte start"
);
assert!(
is_valid_shift_jis(0x9F, 0xFC),
"lower-range end, low-byte end"
);
assert!(
is_valid_shift_jis(0xE0, 0x40),
"upper-range start, low-byte start"
);
assert!(
is_valid_shift_jis(0xEB, 0xBF),
"upper-range end (mid low-byte)"
);
assert!(
!is_valid_shift_jis(0x81, 0x3F),
"lo=0x3F just below 0x40 must reject"
);
assert!(
!is_valid_shift_jis(0x81, 0xFD),
"lo=0xFD just above 0xFC must reject"
);
assert!(
!is_valid_shift_jis(0x81, 0x7F),
"lo=0x7F explicitly excluded (kills `lo != 0x7F` → `lo == 0x7F`)"
);
assert!(
!is_valid_shift_jis(0x80, 0x40),
"val=0x8040 below lower range"
);
assert!(
!is_valid_shift_jis(0xA0, 0x40),
"val=0xA040 in gap between the two ranges \
(kills `||` → `&&` mutant on the OR; that mutant would \
require both ranges to match, but here neither does, so \
this specific witness needs a value that's IN one range \
and not the other instead — see below)"
);
assert!(
is_valid_shift_jis(0x81, 0x40) && is_valid_shift_jis(0xE0, 0x40),
"two single-range anchors prove the OR is real"
);
assert!(
!is_valid_shift_jis(0xEC, 0x40),
"val=0xEC40 above upper range"
);
assert!(!is_valid_shift_jis(0x00, 0x40));
assert!(!is_valid_shift_jis(0xFF, 0xFC));
}
#[test]
fn apply_mask_at_positions_xor_with_sentinel_filter() {
let mut pixs = vec![0i8, 0, 0, 0];
let positions: Vec<usize> = vec![0, 1, 2, 3];
apply_mask_at_positions(&mut pixs, &positions, 0, 2);
assert_eq!(
pixs,
vec![1, 0, 0, 1],
"2x2 mask 0: corners (0,0) and (1,1) flip 0→1"
);
let mut pixs = vec![1i8, 1, 1, 1];
let positions: Vec<usize> = vec![0, 3];
apply_mask_at_positions(&mut pixs, &positions, 0, 2);
assert_eq!(
pixs,
vec![0, 1, 1, 0],
"2x2 mask 0: targeted (0,0) and (1,1) flip; (0,1) and (1,0) left alone"
);
let mut pixs = vec![2i8, 0, 0, 2];
let positions: Vec<usize> = vec![0, 1, 2, 3];
apply_mask_at_positions(&mut pixs, &positions, 0, 2);
assert_eq!(
pixs,
vec![2, 0, 0, 2],
"function-pattern sentinels (=2) must NOT be XORed by the mask"
);
let mut pixs = vec![0i8, 1, 0, 1];
let positions: Vec<usize> = vec![];
apply_mask_at_positions(&mut pixs, &positions, 0, 2);
assert_eq!(pixs, vec![0, 1, 0, 1], "empty positions: pixs untouched");
let mut pixs = vec![0i8, 0, 0, 0];
let positions: Vec<usize> = vec![0, 1, 2, 3];
apply_mask_at_positions(&mut pixs, &positions, 2, 4);
assert_eq!(
pixs,
vec![1, 0, 0, 1],
"1×4 mask 2 (col%3==0): pos=0 (col=0) and pos=3 (col=3) \
flip; pos=1, pos=2 don't. Under the `pos/cu ↔ pos%cu` \
swap mutant, all 4 would flip (since mutant col=0 for all)."
);
}
#[test]
fn place_format_info_reservation_dispatches_per_layout_id_and_counts() {
for &layout_id in &[0u8, 1, 2] {
let rows: u16 = 21;
let cols: u16 = 21;
let mut pixs = vec![-1i8; (rows as usize) * (cols as usize)];
let n = place_format_info_reservation(&mut pixs, layout_id, rows, cols);
assert_eq!(n, 31, "layout_id {layout_id} → Full path; must return 31");
let reserved = pixs.iter().filter(|&&v| v == 1).count();
let dark_zero = pixs.iter().filter(|&&v| v == 0).count();
assert_eq!(
reserved, 30,
"Full QR: 30 cells marked with value 1 (15 TL + 8 TR + 7 BL)"
);
assert_eq!(
dark_zero, 1,
"Full QR: 1 dark module pre-init cell at value 0"
);
}
for &layout_id in &[3u8, 4, 5, 6] {
let rows: u16 = 13;
let cols: u16 = 13;
let mut pixs = vec![-1i8; (rows as usize) * (cols as usize)];
let n = place_format_info_reservation(&mut pixs, layout_id, rows, cols);
assert_eq!(n, 15, "layout_id {layout_id} → Micro path; must return 15");
let reserved = pixs.iter().filter(|&&v| v == 1).count();
assert_eq!(
reserved, 15,
"Micro QR: 15 cells marked with value 1 (L-shape)"
);
}
{
let rows: u16 = 7;
let cols: u16 = 43;
let mut pixs = vec![-1i8; (rows as usize) * (cols as usize)];
let n = place_format_info_reservation(&mut pixs, 7, rows, cols);
assert_eq!(n, 36, "layout_id 7 → Rmqr path; must return 36");
let reserved = pixs.iter().filter(|&&v| v == 1).count();
assert!(
(18..=36).contains(&reserved),
"rMQR R7x43: between 18 and 36 unique cells marked (some pairs may dedupe)"
);
}
{
let mut pixs_full = vec![-1i8; 21 * 21];
let mut pixs_micro = vec![-1i8; 13 * 13];
let n2 = place_format_info_reservation(&mut pixs_full, 2, 21, 21);
let n3 = place_format_info_reservation(&mut pixs_micro, 3, 13, 13);
assert_eq!(n2, 31, "layout_id=2 is the LAST Full QR layout");
assert_eq!(n3, 15, "layout_id=3 is the FIRST Micro QR layout");
}
{
let mut pixs_micro = vec![-1i8; 13 * 13];
let mut pixs_rmqr = vec![-1i8; 7 * 43];
let n6 = place_format_info_reservation(&mut pixs_micro, 6, 13, 13);
let n7 = place_format_info_reservation(&mut pixs_rmqr, 7, 7, 43);
assert_eq!(n6, 15, "layout_id=6 is the LAST Micro QR layout");
assert_eq!(n7, 36, "layout_id=7 is the FIRST Rmqr layout");
}
}
#[test]
fn place_format_info_reservation_full_v1_30_cells_plus_dark_module() {
let rows: u16 = 21;
let cols: u16 = 21;
let mut pixs = vec![-1i8; (rows as usize) * (cols as usize)];
place_format_info_reservation_full(&mut pixs, rows, cols);
let cu = cols as usize;
let ru = rows as usize;
let tl_cells: [(usize, usize); 15] = [
(0, 8),
(1, 8),
(2, 8),
(3, 8),
(4, 8),
(5, 8),
(7, 8),
(8, 8),
(8, 7),
(8, 5),
(8, 4),
(8, 3),
(8, 2),
(8, 1),
(8, 0),
];
for &(r, c) in &tl_cells {
assert_eq!(
pixs[r * cu + c],
FORMAT_INFO_RESERVATION_VALUE,
"TL cluster cell ({r}, {c}) must be reserved"
);
}
for k in 0..8 {
let c = cu - 1 - k;
assert_eq!(
pixs[8 * cu + c],
FORMAT_INFO_RESERVATION_VALUE,
"TR cluster cell (8, {c}) (k={k}) must be reserved"
);
}
assert_eq!(
pixs[8 * cu + 13],
FORMAT_INFO_RESERVATION_VALUE,
"TR cluster MUST include (8, 13); a `0..7` mutant misses it"
);
for k in 1..8 {
let r = ru - 8 + k;
assert_eq!(
pixs[r * cu + 8],
FORMAT_INFO_RESERVATION_VALUE,
"BL cluster cell ({r}, 8) (k={k}) must be reserved"
);
}
assert_eq!(
pixs[20 * cu + 8],
FORMAT_INFO_RESERVATION_VALUE,
"BL cluster MUST include (20, 8); a `1..7` mutant misses it"
);
assert_eq!(
pixs[13 * cu + 8],
0,
"dark module pre-init at (13, 8) must be 0 (not reservation value)"
);
let reserved_count = pixs.iter().filter(|&&v| v == 1).count();
assert_eq!(
reserved_count, 30,
"exactly 30 cells reserved (TL 15 + TR 8 + BL 7)"
);
let mut expected_set: std::collections::HashSet<usize> = std::collections::HashSet::new();
for &(r, c) in &tl_cells {
expected_set.insert(r * cu + c);
}
for k in 0..8 {
expected_set.insert(8 * cu + (cu - 1 - k));
}
for k in 1..8 {
expected_set.insert((ru - 8 + k) * cu + 8);
}
let dark_idx = 13 * cu + 8;
for (idx, &v) in pixs.iter().enumerate() {
if expected_set.contains(&idx) {
assert_eq!(v, 1, "expected-reserved cell {idx} should be 1");
} else if idx == dark_idx {
assert_eq!(v, 0, "dark module idx {idx} should be 0");
} else {
assert_eq!(
v,
-1,
"untouched cell {idx} ({},{}) should be at sentinel -1",
idx / cu,
idx % cu
);
}
}
}
#[test]
fn place_format_info_reservation_micro_l_shape_15_cells() {
let rows: u16 = 13;
let cols: u16 = 13;
let mut pixs = vec![0i8; (rows as usize) * (cols as usize)];
place_format_info_reservation_micro(&mut pixs, rows, cols);
let expected_cells: [(usize, usize); 15] = [
(1, 8),
(2, 8),
(3, 8),
(4, 8),
(5, 8),
(6, 8),
(7, 8),
(8, 8),
(8, 7),
(8, 6),
(8, 5),
(8, 4),
(8, 3),
(8, 2),
(8, 1),
];
let cu = cols as usize;
for &(r, c) in &expected_cells {
assert_eq!(
pixs[r * cu + c],
FORMAT_INFO_RESERVATION_VALUE,
"cell ({r}, {c}) must be marked with FORMAT_INFO_RESERVATION_VALUE"
);
}
let marked_count = pixs.iter().filter(|&&v| v == 1).count();
assert_eq!(
marked_count, 15,
"exactly 15 cells must be marked (catches missing or extra cell)"
);
let expected_set: std::collections::HashSet<usize> =
expected_cells.iter().map(|&(r, c)| r * cu + c).collect();
for (idx, &v) in pixs.iter().enumerate() {
if expected_set.contains(&idx) {
assert_eq!(v, 1);
} else {
assert_eq!(
v,
0,
"cell at idx {idx} ({},{}) should NOT be marked",
idx / cu,
idx % cu
);
}
}
let idx_1_8 = cu + 8;
let idx_8_1 = 8 * cu + 1;
assert_eq!(idx_1_8, 21);
assert_eq!(idx_8_1, 105);
assert_eq!(
idx_8_1 - idx_1_8,
84,
"M2 (13x13) row stride is 13; (8,1)-(1,8) = 7*13-7 = 84"
);
assert_eq!(pixs[idx_1_8], 1);
assert_eq!(pixs[idx_8_1], 1);
}
#[test]
fn apply_lc4b_nibble_fixup_shifts_trailing_ecc_left_by_four_bits() {
let mut stream = [0xAA, 0xBB, 0xCC, 0xDD];
apply_lc4b_nibble_fixup(&mut stream, 2, 4);
assert_eq!(
stream,
[0xAA, 0xBC, 0xCD, 0xD0],
"dcws=2 ncws=4: nibble fixup must shift [0xBB,0xCC,0xDD] \
left by 4 bits, leaving stream[0] untouched"
);
let mut stream = [0x12, 0x34, 0x56];
apply_lc4b_nibble_fixup(&mut stream, 1, 3);
assert_eq!(
stream,
[0x13, 0x45, 0x60],
"dcws=1 ncws=3: nibble fixup must propagate high nibbles \
across the entire stream"
);
let mut stream = [0xAB];
apply_lc4b_nibble_fixup(&mut stream, 1, 1);
assert_eq!(
stream,
[0xA0],
"dcws=ncws=1: shift the half-nibble into the high half \
and zero the low half"
);
let mut stream = [0xFF];
apply_lc4b_nibble_fixup(&mut stream, 0, 0);
assert_eq!(
stream,
[0xFF],
"ncws=0: early return must NOT touch the stream"
);
let mut stream = [0u8; 4];
apply_lc4b_nibble_fixup(&mut stream, 2, 4);
assert_eq!(
stream, [0u8; 4],
"all-zero stream must remain all-zero after the fixup"
);
let mut stream = [0xFFu8; 4];
apply_lc4b_nibble_fixup(&mut stream, 2, 4);
assert_eq!(
stream,
[0xFF, 0xFF, 0xFF, 0xF0],
"all-0xFF stream: tail byte's low nibble must clear to zero"
);
}
#[test]
fn pixs_set_col_overflow_guard_and_idx_overflow_guard() {
const COLS: usize = 4;
const ROWS: usize = 3;
let len = ROWS * COLS;
let make = || vec![-1i8; len];
let mut p = make();
super::pixs_set(&mut p, 0, 0, COLS, 7);
let mut want = make();
want[0] = 7;
assert_eq!(
p, want,
"pixs_set(0,0): in-bounds write must land at idx=0 only"
);
let mut p = make();
super::pixs_set(&mut p, 2, 3, COLS, 5);
let mut want = make();
want[11] = 5;
assert_eq!(
p, want,
"pixs_set(2,3): last-valid in-bounds write must land at idx=11"
);
let mut p = make();
super::pixs_set(&mut p, 0, COLS, COLS, 99);
assert_eq!(
p,
make(),
"pixs_set(0, cols, cols): col-OOB guard must trip before \
qmv wraps into the next row's first cell"
);
let mut p = make();
super::pixs_set(&mut p, 1, COLS, COLS, 88);
assert_eq!(
p,
make(),
"pixs_set(1, cols, cols): col-OOB guard must protect row 2"
);
let mut p = make();
super::pixs_set(&mut p, 0, COLS + 2, COLS, 77);
assert_eq!(
p,
make(),
"pixs_set(0, cols+2, cols): far col-OOB must trip the guard"
);
let mut p = make();
super::pixs_set(&mut p, ROWS, 0, COLS, 11);
assert_eq!(
p,
make(),
"pixs_set(rows, 0, cols): idx-OOB guard must protect the \
buffer when col is valid but row is past the end"
);
let mut p = make();
super::pixs_set(&mut p, ROWS, COLS - 1, COLS, 22);
assert_eq!(
p,
make(),
"pixs_set(rows, cols-1, cols): idx-OOB at idx=15 must trip"
);
let mut p = make();
super::pixs_set(&mut p, 0, 1, COLS, 1);
super::pixs_set(&mut p, 1, 2, COLS, 2);
super::pixs_set(&mut p, 2, 0, COLS, 3);
let mut want = make();
want[1] = 1;
want[6] = 2;
want[8] = 3;
assert_eq!(
p, want,
"pixs_set sequence: each call writes exactly its (row, col) \
at qmv(row, col, cols) with the supplied value"
);
}
#[test]
fn qmv_row_major_index_with_row_col_swap_discriminator() {
assert_eq!(super::qmv(0, 0, 4), 0, "(0, 0, 4) → 0");
assert_eq!(super::qmv(0, 3, 4), 3, "(0, 3, 4) → 3");
assert_eq!(super::qmv(1, 0, 4), 4, "(1, 0, 4) → 4");
assert_eq!(super::qmv(2, 1, 5), 11, "(2, 1, 5) → 11");
assert_eq!(super::qmv(5, 3, 7), 38, "(5, 3, 7) → 38");
assert_eq!(
super::qmv(2, 5, 7),
19,
"row-major: 2*7+5 = 19; swap mutant would give 37"
);
assert_eq!(
super::qmv(5, 2, 7),
37,
"row-major: 5*7+2 = 37; swap mutant would give 19"
);
for r in 0..5usize {
for cols in 1..=8usize {
assert_eq!(
super::qmv(r, cols, cols),
super::qmv(r + 1, 0, cols),
"boundary: qmv(r={r}, cols={cols}, cols) must equal \
qmv(r+1, 0, cols)"
);
}
}
for r in 0..10usize {
assert_eq!(
super::qmv(r, 0, 1),
r,
"single-col: qmv(r={r}, 0, 1) must equal r"
);
}
const R: usize = 4;
const C: usize = 5;
let mut seen = [false; R * C];
for r in 0..R {
for c in 0..C {
let idx = super::qmv(r, c, C);
assert!(
idx < R * C,
"qmv(r={r}, c={c}, cols={C}) = {idx} must be < rows*cols = {}",
R * C
);
assert!(
!seen[idx],
"qmv produced duplicate idx={idx} at (r={r}, c={c})"
);
seen[idx] = true;
}
}
assert!(
seen.iter().all(|&s| s),
"row-major sweep over (R={R}) x (C={C}) must hit every linear idx exactly once"
);
}
#[test]
fn encode_micro_with_options_option_parsing_rejection_arms() {
use crate::Options;
let err = encode_micro_with_options(b"", &Options::default()).unwrap_err();
assert!(
matches!(&err, Error::InvalidData(m) if m.contains("Micro QR")),
"empty Micro input should be InvalidData(Micro QR), got {err:?}"
);
let mut opts = Options::default();
opts.extras.push(("eclevel".into(), "H".into()));
let err = encode_micro_with_options(b"HELLO", &opts).unwrap_err();
assert!(
matches!(&err, Error::InvalidOption(m) if m.contains("does not support EC level H")),
"eclevel=H should be the dedicated H rejection, got {err:?}"
);
let mut opts = Options::default();
opts.extras.push(("eclevel".into(), "foo".into()));
let err = encode_micro_with_options(b"HELLO", &opts).unwrap_err();
assert!(
matches!(&err, Error::InvalidOption(m) if m.contains("eclevel=foo")),
"eclevel=foo should echo back the bad value, got {err:?}"
);
let mut opts = Options::default();
opts.extras.push(("version".into(), "abc".into()));
let err = encode_micro_with_options(b"HELLO", &opts).unwrap_err();
assert!(
matches!(&err, Error::InvalidOption(m) if m.contains("version=abc")),
"version=abc should echo back, got {err:?}"
);
let mut opts = Options::default();
opts.extras.push(("version".into(), "Mabc".into()));
let err = encode_micro_with_options(b"HELLO", &opts).unwrap_err();
assert!(
matches!(&err, Error::InvalidOption(m) if m.contains("version=Mabc")),
"version=Mabc should echo full string, got {err:?}"
);
let mut opts = Options::default();
opts.extras.push(("version".into(), "0".into()));
let err = encode_micro_with_options(b"HELLO", &opts).unwrap_err();
assert!(
matches!(&err, Error::InvalidOption(m)
if m.contains("Micro QR version must be M1..=M4")),
"version=0 should be range-rejected, got {err:?}"
);
let mut opts = Options::default();
opts.extras.push(("version".into(), "5".into()));
let err = encode_micro_with_options(b"HELLO", &opts).unwrap_err();
assert!(
matches!(&err, Error::InvalidOption(m)
if m.contains("Micro QR version must be M1..=M4") && m.contains("M5")),
"version=5 should reject and echo 'M5', got {err:?}"
);
let mut opts = Options::default();
opts.extras.push(("version".into(), "M5".into()));
let err = encode_micro_with_options(b"HELLO", &opts).unwrap_err();
assert!(
matches!(&err, Error::InvalidOption(m)
if m.contains("Micro QR version must be M1..=M4")),
"version=M5 should reject post-strip, got {err:?}"
);
let mut opts = Options::default();
opts.extras.push(("version".into(), "M2".into()));
opts.extras.push(("eclevel".into(), "L".into()));
assert!(
encode_micro_with_options(b"123", &opts).is_ok(),
"M2-L digits should succeed"
);
let mut opts = Options::default();
opts.extras.push(("version".into(), "M4".into()));
opts.extras.push(("eclevel".into(), "L".into()));
assert!(
encode_micro_with_options(b"HELLO", &opts).is_ok(),
"M4-L upper boundary should succeed"
);
}
}