#![allow(dead_code)]
pub(crate) struct HanXinMetric {
pub version: &'static str,
pub size: u8,
pub alig_step: i8,
pub alig_count: u8,
pub cap_bits: u32,
pub blocks: [[Option<(u8, u8, u8)>; 3]; 4],
}
#[allow(dead_code)]
const NOBLK: Option<(u8, u8, u8)> = None;
#[rustfmt::skip]
pub(crate) const HANXIN_METRICS: [HanXinMetric; 84] = [
HanXinMetric { version: "1", size: 23, alig_step: -1, alig_count: 0, cap_bits: 205, blocks: [[Some((1, 21, 4)), None, None], [Some((1, 17, 8)), None, None], [Some((1, 13, 12)), None, None], [Some((1, 9, 16)), None, None]] },
HanXinMetric { version: "2", size: 25, alig_step: -1, alig_count: 0, cap_bits: 301, blocks: [[Some((1, 31, 6)), None, None], [Some((1, 25, 12)), None, None], [Some((1, 19, 18)), None, None], [Some((1, 15, 22)), None, None]] },
HanXinMetric { version: "3", size: 27, alig_step: -1, alig_count: 0, cap_bits: 405, blocks: [[Some((1, 42, 8)), None, None], [Some((1, 34, 16)), None, None], [Some((1, 26, 24)), None, None], [Some((1, 20, 30)), None, None]] },
HanXinMetric { version: "4", size: 29, alig_step: 14, alig_count: 1, cap_bits: 439, blocks: [[Some((1, 46, 8)), None, None], [Some((1, 38, 16)), None, None], [Some((1, 30, 24)), None, None], [Some((1, 22, 32)), None, None]] },
HanXinMetric { version: "5", size: 31, alig_step: 16, alig_count: 1, cap_bits: 555, blocks: [[Some((1, 57, 12)), None, None], [Some((1, 49, 20)), None, None], [Some((1, 37, 32)), None, None], [Some((1, 14, 20)), Some((1, 13, 22)), None]] },
HanXinMetric { version: "6", size: 33, alig_step: 16, alig_count: 1, cap_bits: 675, blocks: [[Some((1, 70, 14)), None, None], [Some((1, 58, 26)), None, None], [Some((1, 24, 20)), Some((1, 22, 18)), None], [Some((1, 16, 24)), Some((1, 18, 26)), None]] },
HanXinMetric { version: "7", size: 35, alig_step: 17, alig_count: 1, cap_bits: 805, blocks: [[Some((1, 84, 16)), None, None], [Some((1, 70, 30)), None, None], [Some((1, 26, 22)), Some((1, 28, 24)), None], [Some((2, 14, 20)), Some((1, 12, 20)), None]] },
HanXinMetric { version: "8", size: 37, alig_step: 18, alig_count: 1, cap_bits: 943, blocks: [[Some((1, 99, 18)), None, None], [Some((1, 40, 18)), Some((1, 41, 18)), None], [Some((1, 31, 26)), Some((1, 32, 28)), None], [Some((2, 16, 24)), Some((1, 15, 22)), None]] },
HanXinMetric { version: "9", size: 39, alig_step: 19, alig_count: 1, cap_bits: 1089, blocks: [[Some((1, 114, 22)), None, None], [Some((2, 48, 20)), None, None], [Some((2, 24, 20)), Some((1, 26, 22)), None], [Some((2, 18, 28)), Some((1, 18, 26)), None]] },
HanXinMetric { version: "10", size: 41, alig_step: 20, alig_count: 1, cap_bits: 1243, blocks: [[Some((1, 131, 24)), None, None], [Some((1, 52, 22)), Some((1, 57, 24)), None], [Some((2, 27, 24)), Some((1, 29, 24)), None], [Some((2, 21, 32)), Some((1, 19, 30)), None]] },
HanXinMetric { version: "11", size: 43, alig_step: 14, alig_count: 2, cap_bits: 1289, blocks: [[Some((1, 135, 26)), None, None], [Some((1, 56, 24)), Some((1, 57, 24)), None], [Some((2, 28, 24)), Some((1, 31, 26)), None], [Some((2, 22, 32)), Some((1, 21, 32)), None]] },
HanXinMetric { version: "12", size: 45, alig_step: 15, alig_count: 2, cap_bits: 1455, blocks: [[Some((1, 153, 28)), None, None], [Some((1, 62, 26)), Some((1, 65, 28)), None], [Some((2, 32, 28)), Some((1, 33, 28)), None], [Some((3, 17, 26)), Some((1, 22, 30)), None]] },
HanXinMetric { version: "13", size: 47, alig_step: 16, alig_count: 2, cap_bits: 1629, blocks: [[Some((1, 86, 16)), Some((1, 85, 16)), None], [Some((1, 71, 30)), Some((1, 72, 30)), None], [Some((2, 37, 32)), Some((1, 35, 30)), None], [Some((3, 20, 30)), Some((1, 21, 32)), None]] },
HanXinMetric { version: "14", size: 49, alig_step: 16, alig_count: 2, cap_bits: 1805, blocks: [[Some((1, 94, 18)), Some((1, 95, 18)), None], [Some((2, 51, 22)), Some((1, 55, 24)), None], [Some((3, 30, 26)), Some((1, 31, 26)), None], [Some((4, 18, 28)), Some((1, 17, 24)), None]] },
HanXinMetric { version: "15", size: 51, alig_step: 17, alig_count: 2, cap_bits: 1995, blocks: [[Some((1, 104, 20)), Some((1, 105, 20)), None], [Some((2, 57, 24)), Some((1, 61, 26)), None], [Some((3, 33, 28)), Some((1, 36, 30)), None], [Some((4, 20, 30)), Some((1, 19, 30)), None]] },
HanXinMetric { version: "16", size: 53, alig_step: 17, alig_count: 2, cap_bits: 2187, blocks: [[Some((1, 115, 22)), Some((1, 114, 22)), None], [Some((2, 65, 28)), Some((1, 61, 26)), None], [Some((3, 38, 32)), Some((1, 33, 30)), None], [Some((5, 19, 28)), Some((1, 14, 24)), None]] },
HanXinMetric { version: "17", size: 55, alig_step: 18, alig_count: 2, cap_bits: 2393, blocks: [[Some((1, 126, 24)), Some((1, 125, 24)), None], [Some((2, 70, 30)), Some((1, 69, 30)), None], [Some((4, 33, 28)), Some((1, 29, 26)), None], [Some((5, 20, 30)), Some((1, 19, 30)), None]] },
HanXinMetric { version: "18", size: 57, alig_step: 19, alig_count: 2, cap_bits: 2607, blocks: [[Some((1, 136, 26)), Some((1, 137, 26)), None], [Some((3, 56, 24)), Some((1, 59, 26)), None], [Some((5, 35, 30)), None, None], [Some((6, 18, 28)), Some((1, 21, 28)), None]] },
HanXinMetric { version: "19", size: 59, alig_step: 20, alig_count: 2, cap_bits: 2829, blocks: [[Some((1, 148, 28)), Some((1, 149, 28)), None], [Some((3, 61, 26)), Some((1, 64, 28)), None], [Some((7, 24, 20)), Some((1, 23, 22)), None], [Some((6, 20, 30)), Some((1, 21, 32)), None]] },
HanXinMetric { version: "20", size: 61, alig_step: 20, alig_count: 2, cap_bits: 3053, blocks: [[Some((3, 107, 20)), None, None], [Some((3, 65, 28)), Some((1, 72, 30)), None], [Some((7, 26, 22)), Some((1, 23, 22)), None], [Some((7, 19, 28)), Some((1, 20, 32)), None]] },
HanXinMetric { version: "21", size: 63, alig_step: 21, alig_count: 2, cap_bits: 3291, blocks: [[Some((3, 115, 22)), None, None], [Some((4, 56, 24)), Some((1, 63, 28)), None], [Some((7, 28, 24)), Some((1, 25, 22)), None], [Some((8, 18, 28)), Some((1, 21, 22)), None]] },
HanXinMetric { version: "22", size: 65, alig_step: 16, alig_count: 3, cap_bits: 3383, blocks: [[Some((2, 116, 22)), Some((1, 122, 24)), None], [Some((4, 56, 24)), Some((1, 72, 30)), None], [Some((7, 28, 24)), Some((1, 32, 26)), None], [Some((8, 18, 28)), Some((1, 24, 30)), None]] },
HanXinMetric { version: "23", size: 67, alig_step: 17, alig_count: 3, cap_bits: 3631, blocks: [[Some((3, 127, 24)), None, None], [Some((5, 51, 22)), Some((1, 62, 26)), None], [Some((7, 30, 26)), Some((1, 35, 26)), None], [Some((8, 20, 30)), Some((1, 21, 32)), None]] },
HanXinMetric { version: "24", size: 69, alig_step: 17, alig_count: 3, cap_bits: 3887, blocks: [[Some((2, 135, 26)), Some((1, 137, 26)), None], [Some((5, 56, 24)), Some((1, 59, 26)), None], [Some((7, 33, 28)), Some((1, 30, 28)), None], [Some((11, 16, 24)), Some((1, 19, 26)), None]] },
HanXinMetric { version: "25", size: 71, alig_step: 18, alig_count: 3, cap_bits: 4151, blocks: [[Some((3, 105, 20)), Some((1, 121, 22)), None], [Some((5, 61, 26)), Some((1, 57, 26)), None], [Some((9, 28, 24)), Some((1, 28, 22)), None], [Some((10, 19, 28)), Some((1, 18, 30)), None]] },
HanXinMetric { version: "26", size: 73, alig_step: 18, alig_count: 3, cap_bits: 4423, blocks: [[Some((2, 157, 30)), Some((1, 150, 28)), None], [Some((5, 65, 28)), Some((1, 61, 26)), None], [Some((8, 33, 28)), Some((1, 34, 30)), None], [Some((10, 19, 28)), Some((2, 15, 26)), None]] },
HanXinMetric { version: "27", size: 75, alig_step: 19, alig_count: 3, cap_bits: 4703, blocks: [[Some((3, 126, 24)), Some((1, 115, 22)), None], [Some((7, 51, 22)), Some((1, 54, 22)), None], [Some((8, 35, 30)), Some((1, 37, 30)), None], [Some((15, 15, 22)), Some((1, 10, 22)), None]] },
HanXinMetric { version: "28", size: 77, alig_step: 19, alig_count: 3, cap_bits: 4991, blocks: [[Some((4, 105, 20)), Some((1, 103, 20)), None], [Some((7, 56, 24)), Some((1, 45, 18)), None], [Some((10, 31, 26)), Some((1, 27, 26)), None], [Some((10, 17, 26)), Some((3, 20, 28)), Some((1, 21, 28))]] },
HanXinMetric { version: "29", size: 79, alig_step: 20, alig_count: 3, cap_bits: 5287, blocks: [[Some((3, 139, 26)), Some((1, 137, 28)), None], [Some((6, 66, 28)), Some((1, 66, 30)), None], [Some((9, 36, 30)), Some((1, 34, 32)), None], [Some((13, 19, 28)), Some((1, 17, 32)), None]] },
HanXinMetric { version: "30", size: 81, alig_step: 20, alig_count: 3, cap_bits: 5591, blocks: [[Some((6, 84, 16)), Some((1, 82, 16)), None], [Some((6, 70, 30)), Some((1, 68, 30)), None], [Some((7, 35, 30)), Some((3, 33, 28)), Some((1, 32, 28))], [Some((13, 20, 30)), Some((1, 20, 28)), None]] },
HanXinMetric { version: "31", size: 83, alig_step: 21, alig_count: 3, cap_bits: 5903, blocks: [[Some((5, 105, 20)), Some((1, 94, 18)), None], [Some((6, 74, 32)), Some((1, 71, 30)), None], [Some((11, 33, 28)), Some((1, 34, 32)), None], [Some((13, 19, 28)), Some((3, 16, 26)), None]] },
HanXinMetric { version: "32", size: 85, alig_step: 17, alig_count: 4, cap_bits: 6033, blocks: [[Some((4, 127, 24)), Some((1, 126, 24)), None], [Some((7, 66, 28)), Some((1, 66, 30)), None], [Some((12, 30, 24)), Some((1, 24, 28)), Some((1, 24, 30))], [Some((15, 19, 28)), Some((1, 17, 32)), None]] },
HanXinMetric { version: "33", size: 87, alig_step: 17, alig_count: 4, cap_bits: 6353, blocks: [[Some((7, 84, 16)), Some((1, 78, 16)), None], [Some((7, 70, 30)), Some((1, 66, 28)), None], [Some((12, 33, 28)), Some((1, 32, 30)), None], [Some((14, 21, 32)), Some((1, 24, 28)), None]] },
HanXinMetric { version: "34", size: 89, alig_step: 18, alig_count: 4, cap_bits: 6689, blocks: [[Some((5, 117, 22)), Some((1, 117, 24)), None], [Some((8, 66, 28)), Some((1, 58, 26)), None], [Some((11, 38, 32)), Some((1, 34, 32)), None], [Some((15, 20, 30)), Some((2, 17, 26)), None]] },
HanXinMetric { version: "35", size: 91, alig_step: 18, alig_count: 4, cap_bits: 7025, blocks: [[Some((4, 148, 28)), Some((1, 146, 28)), None], [Some((8, 68, 30)), Some((1, 70, 24)), None], [Some((10, 36, 32)), Some((3, 38, 28)), None], [Some((16, 19, 28)), Some((3, 16, 26)), None]] },
HanXinMetric { version: "36", size: 93, alig_step: 19, alig_count: 4, cap_bits: 7377, blocks: [[Some((4, 126, 24)), Some((2, 135, 26)), None], [Some((8, 70, 28)), Some((2, 43, 26)), None], [Some((13, 32, 28)), Some((2, 41, 30)), None], [Some((17, 19, 28)), Some((3, 15, 26)), None]] },
HanXinMetric { version: "37", size: 95, alig_step: 19, alig_count: 4, cap_bits: 7729, blocks: [[Some((5, 136, 26)), Some((1, 132, 24)), None], [Some((5, 67, 30)), Some((4, 68, 28)), Some((1, 69, 28))], [Some((14, 35, 30)), Some((1, 32, 24)), None], [Some((18, 18, 26)), Some((3, 16, 28)), Some((1, 14, 28))]] },
HanXinMetric { version: "38", size: 97, alig_step: 19, alig_count: 4, cap_bits: 8089, blocks: [[Some((3, 142, 26)), Some((3, 141, 28)), None], [Some((8, 70, 30)), Some((1, 73, 32)), Some((1, 74, 32))], [Some((12, 34, 30)), Some((3, 34, 26)), Some((1, 35, 28))], [Some((18, 21, 32)), Some((1, 27, 30)), None]] },
HanXinMetric { version: "39", size: 99, alig_step: 20, alig_count: 4, cap_bits: 8465, blocks: [[Some((5, 116, 22)), Some((2, 103, 20)), Some((1, 102, 20))], [Some((9, 74, 32)), Some((1, 74, 30)), None], [Some((14, 34, 28)), Some((2, 32, 32)), Some((1, 32, 30))], [Some((19, 21, 32)), Some((1, 25, 26)), None]] },
HanXinMetric { version: "40", size: 101, alig_step: 20, alig_count: 4, cap_bits: 8841, blocks: [[Some((7, 116, 22)), Some((1, 117, 22)), None], [Some((11, 65, 28)), Some((1, 58, 24)), None], [Some((15, 38, 32)), Some((1, 27, 28)), None], [Some((20, 20, 30)), Some((1, 20, 32)), Some((1, 21, 32))]] },
HanXinMetric { version: "41", size: 103, alig_step: 17, alig_count: 5, cap_bits: 9009, blocks: [[Some((6, 136, 26)), Some((1, 130, 24)), None], [Some((11, 66, 28)), Some((1, 62, 30)), None], [Some((14, 34, 28)), Some((3, 34, 32)), Some((1, 30, 30))], [Some((18, 20, 30)), Some((3, 20, 28)), Some((2, 15, 26))]] },
HanXinMetric { version: "42", size: 105, alig_step: 17, alig_count: 5, cap_bits: 9401, blocks: [[Some((5, 105, 20)), Some((2, 115, 22)), Some((2, 116, 22))], [Some((10, 75, 32)), Some((1, 73, 32)), None], [Some((16, 38, 32)), Some((1, 27, 28)), None], [Some((22, 19, 28)), Some((2, 16, 30)), Some((1, 19, 30))]] },
HanXinMetric { version: "43", size: 107, alig_step: 18, alig_count: 5, cap_bits: 9799, blocks: [[Some((6, 147, 28)), Some((1, 146, 28)), None], [Some((11, 66, 28)), Some((2, 65, 30)), None], [Some((18, 33, 28)), Some((2, 33, 30)), None], [Some((22, 21, 32)), Some((1, 28, 30)), None]] },
HanXinMetric { version: "44", size: 109, alig_step: 18, alig_count: 5, cap_bits: 10207, blocks: [[Some((6, 116, 22)), Some((3, 125, 24)), None], [Some((11, 75, 32)), Some((1, 68, 30)), None], [Some((13, 35, 28)), Some((6, 34, 32)), Some((1, 30, 30))], [Some((23, 21, 32)), Some((1, 26, 30)), None]] },
HanXinMetric { version: "45", size: 111, alig_step: 18, alig_count: 5, cap_bits: 10623, blocks: [[Some((7, 105, 20)), Some((4, 95, 18)), None], [Some((12, 67, 28)), Some((1, 63, 30)), Some((1, 62, 32))], [Some((21, 31, 26)), Some((2, 33, 32)), None], [Some((23, 21, 32)), Some((2, 24, 30)), None]] },
HanXinMetric { version: "46", size: 113, alig_step: 19, alig_count: 5, cap_bits: 11045, blocks: [[Some((10, 116, 22)), None, None], [Some((12, 74, 32)), Some((1, 78, 30)), None], [Some((18, 37, 32)), Some((1, 39, 30)), Some((1, 41, 28))], [Some((25, 21, 32)), Some((1, 27, 28)), None]] },
HanXinMetric { version: "47", size: 115, alig_step: 19, alig_count: 5, cap_bits: 11477, blocks: [[Some((5, 126, 24)), Some((4, 115, 22)), Some((1, 114, 22))], [Some((12, 67, 28)), Some((2, 66, 32)), Some((1, 68, 30))], [Some((21, 35, 30)), Some((1, 39, 30)), None], [Some((26, 21, 32)), Some((1, 28, 28)), None]] },
HanXinMetric { version: "48", size: 117, alig_step: 19, alig_count: 5, cap_bits: 11917, blocks: [[Some((9, 126, 24)), Some((1, 117, 22)), None], [Some((13, 75, 32)), Some((1, 68, 30)), None], [Some((20, 35, 30)), Some((3, 35, 28)), None], [Some((27, 21, 32)), Some((1, 28, 30)), None]] },
HanXinMetric { version: "49", size: 119, alig_step: 17, alig_count: 6, cap_bits: 12111, blocks: [[Some((9, 126, 24)), Some((1, 137, 26)), None], [Some((13, 71, 30)), Some((2, 68, 32)), None], [Some((20, 37, 32)), Some((1, 39, 28)), Some((1, 38, 28))], [Some((24, 20, 32)), Some((5, 25, 28)), None]] },
HanXinMetric { version: "50", size: 121, alig_step: 17, alig_count: 6, cap_bits: 12559, blocks: [[Some((8, 147, 28)), Some((1, 141, 28)), None], [Some((10, 73, 32)), Some((4, 74, 30)), Some((1, 73, 30))], [Some((16, 36, 32)), Some((6, 39, 30)), Some((1, 37, 30))], [Some((27, 21, 32)), Some((3, 20, 26)), None]] },
HanXinMetric { version: "51", size: 123, alig_step: 18, alig_count: 6, cap_bits: 13025, blocks: [[Some((9, 137, 26)), Some((1, 135, 26)), None], [Some((12, 70, 30)), Some((4, 75, 32)), None], [Some((24, 35, 30)), Some((1, 40, 28)), None], [Some((23, 20, 32)), Some((8, 24, 30)), None]] },
HanXinMetric { version: "52", size: 125, alig_step: 18, alig_count: 6, cap_bits: 13489, blocks: [[Some((14, 95, 18)), Some((1, 86, 18)), None], [Some((13, 73, 32)), Some((3, 77, 30)), None], [Some((24, 35, 30)), Some((2, 35, 28)), None], [Some((26, 21, 32)), Some((5, 21, 30)), Some((1, 23, 30))]] },
HanXinMetric { version: "53", size: 127, alig_step: 18, alig_count: 6, cap_bits: 13961, blocks: [[Some((9, 147, 28)), Some((1, 142, 28)), None], [Some((10, 73, 30)), Some((6, 70, 32)), Some((1, 71, 32))], [Some((25, 35, 30)), Some((2, 34, 26)), None], [Some((29, 21, 32)), Some((4, 22, 30)), None]] },
HanXinMetric { version: "54", size: 129, alig_step: 18, alig_count: 6, cap_bits: 14441, blocks: [[Some((11, 126, 24)), Some((1, 131, 24)), None], [Some((16, 74, 32)), Some((1, 79, 30)), None], [Some((25, 38, 32)), Some((1, 25, 30)), None], [Some((33, 21, 32)), Some((1, 28, 28)), None]] },
HanXinMetric { version: "55", size: 131, alig_step: 19, alig_count: 6, cap_bits: 14939, blocks: [[Some((14, 105, 20)), Some((1, 99, 18)), None], [Some((19, 65, 28)), Some((1, 72, 28)), None], [Some((24, 37, 32)), Some((2, 40, 30)), Some((1, 41, 30))], [Some((31, 21, 32)), Some((4, 24, 32)), None]] },
HanXinMetric { version: "56", size: 133, alig_step: 19, alig_count: 6, cap_bits: 15435, blocks: [[Some((10, 147, 28)), Some((1, 151, 28)), None], [Some((15, 71, 30)), Some((3, 71, 32)), Some((1, 73, 32))], [Some((24, 37, 32)), Some((3, 38, 30)), Some((1, 39, 30))], [Some((36, 19, 30)), Some((3, 29, 26)), None]] },
HanXinMetric { version: "57", size: 135, alig_step: 19, alig_count: 6, cap_bits: 15939, blocks: [[Some((15, 105, 20)), Some((1, 99, 18)), None], [Some((19, 70, 30)), Some((1, 64, 28)), None], [Some((27, 38, 32)), Some((2, 25, 26)), None], [Some((38, 20, 30)), Some((2, 18, 28)), None]] },
HanXinMetric { version: "58", size: 137, alig_step: 17, alig_count: 7, cap_bits: 16171, blocks: [[Some((14, 105, 20)), Some((1, 113, 22)), Some((1, 114, 22))], [Some((17, 67, 30)), Some((3, 92, 32)), None], [Some((30, 35, 30)), Some((1, 41, 30)), None], [Some((36, 21, 32)), Some((1, 26, 30)), Some((1, 27, 30))]] },
HanXinMetric { version: "59", size: 139, alig_step: 17, alig_count: 7, cap_bits: 16691, blocks: [[Some((11, 146, 28)), Some((1, 146, 26)), None], [Some((20, 70, 30)), Some((1, 60, 26)), None], [Some((29, 38, 32)), Some((1, 24, 32)), None], [Some((40, 20, 30)), Some((2, 17, 26)), None]] },
HanXinMetric { version: "60", size: 141, alig_step: 18, alig_count: 7, cap_bits: 17215, blocks: [[Some((3, 137, 26)), Some((1, 136, 26)), Some((10, 126, 24))], [Some((22, 65, 28)), Some((1, 75, 30)), None], [Some((30, 37, 32)), Some((1, 51, 30)), None], [Some((42, 20, 30)), Some((1, 21, 30)), None]] },
HanXinMetric { version: "61", size: 143, alig_step: 18, alig_count: 7, cap_bits: 17751, blocks: [[Some((12, 126, 24)), Some((2, 118, 22)), Some((1, 116, 22))], [Some((19, 74, 32)), Some((1, 74, 30)), Some((1, 72, 28))], [Some((30, 38, 32)), Some((2, 29, 30)), None], [Some((39, 20, 32)), Some((2, 37, 26)), Some((1, 38, 26))]] },
HanXinMetric { version: "62", size: 145, alig_step: 18, alig_count: 7, cap_bits: 18295, blocks: [[Some((12, 126, 24)), Some((3, 136, 26)), None], [Some((21, 70, 30)), Some((2, 65, 28)), None], [Some((34, 35, 30)), Some((1, 44, 32)), None], [Some((42, 20, 30)), Some((2, 19, 28)), Some((2, 18, 28))]] },
HanXinMetric { version: "63", size: 147, alig_step: 18, alig_count: 7, cap_bits: 18847, blocks: [[Some((12, 126, 24)), Some((3, 117, 22)), Some((1, 116, 22))], [Some((25, 61, 26)), Some((2, 62, 28)), None], [Some((34, 35, 30)), Some((1, 40, 32)), Some((1, 41, 32))], [Some((45, 20, 30)), Some((1, 20, 32)), Some((1, 21, 32))]] },
HanXinMetric { version: "64", size: 149, alig_step: 19, alig_count: 7, cap_bits: 19403, blocks: [[Some((15, 105, 20)), Some((2, 115, 22)), Some((2, 116, 22))], [Some((25, 65, 28)), Some((1, 72, 28)), None], [Some((18, 35, 30)), Some((17, 37, 32)), Some((1, 50, 32))], [Some((42, 20, 30)), Some((6, 19, 28)), Some((1, 15, 28))]] },
HanXinMetric { version: "65", size: 151, alig_step: 19, alig_count: 7, cap_bits: 19971, blocks: [[Some((19, 105, 20)), Some((1, 101, 20)), None], [Some((33, 51, 22)), Some((1, 65, 22)), None], [Some((40, 33, 28)), Some((1, 28, 28)), None], [Some((49, 20, 30)), Some((1, 18, 28)), None]] },
HanXinMetric { version: "66", size: 153, alig_step: 17, alig_count: 8, cap_bits: 20229, blocks: [[Some((18, 105, 20)), Some((2, 117, 22)), None], [Some((26, 65, 28)), Some((1, 80, 30)), None], [Some((35, 35, 30)), Some((3, 35, 28)), Some((1, 36, 28))], [Some((52, 18, 28)), Some((2, 38, 30)), None]] },
HanXinMetric { version: "67", size: 155, alig_step: 17, alig_count: 8, cap_bits: 20805, blocks: [[Some((26, 84, 16)), None, None], [Some((26, 70, 30)), None, None], [Some((45, 31, 26)), Some((1, 9, 26)), None], [Some((52, 20, 30)), None, None]] },
HanXinMetric { version: "68", size: 157, alig_step: 17, alig_count: 8, cap_bits: 21389, blocks: [[Some((16, 126, 24)), Some((1, 114, 22)), Some((1, 115, 22))], [Some((23, 70, 30)), Some((3, 65, 28)), Some((1, 66, 28))], [Some((40, 35, 30)), Some((1, 43, 30)), None], [Some((46, 20, 30)), Some((7, 19, 28)), Some((1, 16, 28))]] },
HanXinMetric { version: "69", size: 159, alig_step: 18, alig_count: 8, cap_bits: 21993, blocks: [[Some((19, 116, 22)), Some((1, 105, 22)), None], [Some((20, 70, 30)), Some((7, 66, 28)), Some((1, 63, 28))], [Some((40, 35, 30)), Some((1, 42, 32)), Some((1, 43, 32))], [Some((54, 20, 30)), Some((1, 19, 30)), None]] },
HanXinMetric { version: "70", size: 161, alig_step: 18, alig_count: 8, cap_bits: 22593, blocks: [[Some((17, 126, 24)), Some((2, 115, 22)), None], [Some((24, 70, 30)), Some((4, 74, 32)), None], [Some((48, 31, 26)), Some((2, 18, 26)), None], [Some((54, 19, 28)), Some((6, 15, 26)), Some((1, 14, 26))]] },
HanXinMetric { version: "71", size: 163, alig_step: 18, alig_count: 8, cap_bits: 23201, blocks: [[Some((29, 84, 16)), None, None], [Some((29, 70, 30)), None, None], [Some((6, 34, 30)), Some((3, 36, 30)), Some((38, 33, 28))], [Some((58, 20, 30)), None, None]] },
HanXinMetric { version: "72", size: 165, alig_step: 18, alig_count: 8, cap_bits: 23817, blocks: [[Some((16, 147, 28)), Some((1, 149, 28)), None], [Some((31, 66, 28)), Some((1, 37, 26)), None], [Some((48, 33, 28)), Some((1, 23, 26)), None], [Some((53, 20, 30)), Some((6, 19, 28)), Some((1, 17, 28))]] },
HanXinMetric { version: "73", size: 167, alig_step: 19, alig_count: 8, cap_bits: 24453, blocks: [[Some((20, 115, 22)), Some((2, 134, 24)), None], [Some((29, 66, 28)), Some((2, 56, 26)), Some((2, 57, 26))], [Some((45, 36, 30)), Some((2, 15, 28)), None], [Some((59, 20, 30)), Some((2, 21, 32)), None]] },
HanXinMetric { version: "74", size: 169, alig_step: 19, alig_count: 8, cap_bits: 25085, blocks: [[Some((17, 147, 28)), Some((1, 134, 26)), None], [Some((26, 70, 30)), Some((5, 75, 32)), None], [Some((47, 35, 30)), Some((1, 48, 32)), None], [Some((64, 18, 28)), Some((2, 33, 30)), Some((1, 35, 30))]] },
HanXinMetric { version: "75", size: 171, alig_step: 17, alig_count: 9, cap_bits: 25373, blocks: [[Some((22, 115, 22)), Some((1, 133, 24)), None], [Some((33, 65, 28)), Some((1, 74, 28)), None], [Some((43, 36, 30)), Some((5, 27, 28)), Some((1, 30, 28))], [Some((57, 20, 30)), Some((5, 21, 32)), Some((1, 24, 32))]] },
HanXinMetric { version: "76", size: 173, alig_step: 17, alig_count: 9, cap_bits: 26021, blocks: [[Some((18, 136, 26)), Some((2, 142, 26)), None], [Some((33, 66, 28)), Some((2, 49, 26)), None], [Some((48, 35, 30)), Some((2, 38, 28)), None], [Some((64, 20, 30)), Some((1, 20, 32)), None]] },
HanXinMetric { version: "77", size: 175, alig_step: 17, alig_count: 9, cap_bits: 26677, blocks: [[Some((19, 126, 24)), Some((2, 135, 26)), Some((1, 136, 26))], [Some((32, 66, 28)), Some((2, 55, 26)), Some((2, 56, 26))], [Some((49, 36, 30)), Some((2, 18, 32)), None], [Some((65, 18, 28)), Some((5, 27, 30)), Some((1, 29, 30))]] },
HanXinMetric { version: "78", size: 177, alig_step: 18, alig_count: 9, cap_bits: 27335, blocks: [[Some((20, 137, 26)), Some((1, 130, 26)), None], [Some((30, 75, 32)), Some((2, 71, 32)), None], [Some((46, 35, 30)), Some((6, 39, 32)), None], [Some((3, 12, 30)), Some((70, 19, 28)), None]] },
HanXinMetric { version: "79", size: 179, alig_step: 18, alig_count: 9, cap_bits: 28007, blocks: [[Some((20, 147, 28)), None, None], [Some((35, 70, 30)), None, None], [Some((49, 35, 30)), Some((5, 35, 28)), None], [Some((70, 20, 30)), None, None]] },
HanXinMetric { version: "80", size: 181, alig_step: 18, alig_count: 9, cap_bits: 28687, blocks: [[Some((21, 136, 26)), Some((1, 155, 28)), None], [Some((34, 70, 30)), Some((1, 64, 28)), Some((1, 65, 28))], [Some((54, 35, 30)), Some((1, 45, 30)), None], [Some((68, 20, 30)), Some((3, 18, 28)), Some((1, 19, 28))]] },
HanXinMetric { version: "81", size: 183, alig_step: 18, alig_count: 9, cap_bits: 29375, blocks: [[Some((19, 126, 24)), Some((5, 115, 22)), Some((1, 114, 22))], [Some((33, 70, 30)), Some((3, 65, 28)), Some((1, 64, 28))], [Some((52, 35, 30)), Some((3, 41, 32)), Some((1, 40, 32))], [Some((67, 20, 30)), Some((5, 21, 32)), Some((1, 24, 32))]] },
HanXinMetric { version: "82", size: 185, alig_step: 18, alig_count: 9, cap_bits: 30071, blocks: [[Some((2, 150, 28)), Some((21, 136, 26)), None], [Some((32, 70, 30)), Some((6, 65, 28)), None], [Some((52, 38, 32)), Some((2, 27, 32)), None], [Some((73, 20, 30)), Some((2, 22, 32)), None]] },
HanXinMetric { version: "83", size: 187, alig_step: 17, alig_count: 10, cap_bits: 30387, blocks: [[Some((21, 126, 24)), Some((4, 136, 26)), None], [Some((30, 74, 32)), Some((6, 73, 30)), None], [Some((54, 35, 30)), Some((4, 40, 32)), None], [Some((75, 20, 30)), Some((1, 20, 28)), None]] },
HanXinMetric { version: "84", size: 189, alig_step: 17, alig_count: 10, cap_bits: 31091, blocks: [[Some((30, 105, 20)), Some((1, 114, 22)), None], [Some((3, 45, 22)), Some((55, 47, 20)), None], [Some((2, 26, 26)), Some((62, 33, 28)), None], [Some((79, 18, 28)), Some((4, 33, 30)), None]] },
];
#[rustfmt::skip]
pub(crate) const FPAT: [[u8; 8]; 8] = [
[1, 1, 1, 1, 1, 1, 1, 0],
[1, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 1, 1, 1, 1, 1, 0],
[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],
[0, 0, 0, 0, 0, 0, 0, 0],
];
#[rustfmt::skip]
pub(crate) const FPAT2: [[u8; 8]; 8] = [
[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, 0],
[1, 1, 1, 1, 1, 0, 1, 0],
[0, 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) const GF256_HANXIN: crate::util::rs_gf2k::GfParams = crate::util::rs_gf2k::GfParams {
size: 256,
poly: 355,
};
pub(crate) const GF16_HANXIN: crate::util::rs_gf2k::GfParams =
crate::util::rs_gf2k::GfParams { size: 16, poly: 19 };
pub(crate) fn rs_encode_gf256(data: &[u8], ecc_count: usize) -> Vec<u8> {
let data_u32: Vec<u32> = data.iter().map(|&b| u32::from(b)).collect();
let check = crate::util::rs_gf2k::encode_k(&data_u32, ecc_count, GF256_HANXIN);
check.into_iter().rev().map(|x| x as u8).collect()
}
pub(crate) fn rs_encode_gf16(data: &[u8], ecc_count: usize) -> Vec<u8> {
let data_u32: Vec<u32> = data.iter().map(|&b| u32::from(b)).collect();
let check = crate::util::rs_gf2k::encode_k(&data_u32, ecc_count, GF16_HANXIN);
check.into_iter().rev().map(|x| x as u8).collect()
}
pub(crate) fn encode_binary_bits(data: &[u8]) -> Result<Vec<bool>, crate::error::Error> {
let msglen = data.len();
if msglen >= 8192 {
return Err(crate::error::Error::InvalidData(format!(
"Han Xin Code: input length {msglen} exceeds 13-bit length field max (8191)",
)));
}
let mut bits = Vec::with_capacity(17 + msglen * 8);
bits.push(false);
bits.push(false);
bits.push(true);
bits.push(true);
for i in (0..13).rev() {
bits.push((msglen >> i) & 1 == 1);
}
for &b in data {
for i in (0..8).rev() {
bits.push((usize::from(b) >> i) & 1 == 1);
}
}
debug_assert_eq!(bits.len(), 17 + msglen * 8);
Ok(bits)
}
#[allow(clippy::type_complexity)]
pub(crate) fn select_version(
bits_len: usize,
eclevel: u8,
) -> Option<(usize, u32, u32, [Option<(u8, u8, u8)>; 3])> {
let level_idx = (eclevel.saturating_sub(1)) as usize;
if level_idx >= 4 {
return None;
}
for (i, m) in HANXIN_METRICS.iter().enumerate() {
let ncws = m.cap_bits / 8;
let level = m.blocks[level_idx];
let ecws: u32 = level
.iter()
.filter_map(|b| *b)
.map(|(c, _d, e)| u32::from(c) * u32::from(e))
.sum();
let dcws = ncws - ecws;
let dmod = dcws * 8;
if bits_len <= dmod as usize {
return Some((i, ncws, dcws, level));
}
}
None
}
pub(crate) fn bits_to_codewords(bits: &[bool]) -> Vec<u8> {
let n_cws = bits.len() / 8;
let mut out = Vec::with_capacity(n_cws);
for cw_idx in 0..n_cws {
let mut cw = 0u8;
for bit_idx in 0..8 {
if bits[cw_idx * 8 + bit_idx] {
cw |= 1 << (7 - bit_idx);
}
}
out.push(cw);
}
out
}
pub(crate) fn encode_rs_blocks(
data_cws: &[u8],
block_layout: &[Option<(u8, u8, u8)>; 3],
) -> Vec<u8> {
let mut data_out: Vec<u8> = Vec::new();
let mut ecc_out: Vec<u8> = Vec::new();
let mut data_pos = 0usize;
for block in block_layout.iter().flatten() {
let (count, dsize, esize) = (block.0 as usize, block.1 as usize, block.2 as usize);
for _ in 0..count {
let chunk = &data_cws[data_pos..data_pos + dsize];
let ecc = rs_encode_gf256(chunk, esize);
data_out.extend_from_slice(chunk);
ecc_out.extend_from_slice(&ecc);
data_pos += dsize;
}
}
debug_assert_eq!(data_pos, data_cws.len());
let mut combined = data_out;
combined.extend_from_slice(&ecc_out);
combined
}
pub(crate) fn pad_to_dmod(bits: &mut Vec<bool>, dmod: usize) {
while bits.len() < dmod {
bits.push(false);
}
}
pub(crate) fn encode_codewords(
data: &[u8],
eclevel: u8,
) -> Result<(usize, Vec<u8>), crate::error::Error> {
let mut bits = encode_binary_bits(data)?;
let (idx, _ncws, dcws, layout) = select_version(bits.len(), eclevel).ok_or_else(|| {
crate::error::Error::InvalidData(format!(
"Han Xin Code: input ({} bits at L{eclevel}) exceeds the largest version",
bits.len(),
))
})?;
let dmod = (dcws as usize) * 8;
pad_to_dmod(&mut bits, dmod);
let data_cws = bits_to_codewords(&bits);
debug_assert_eq!(data_cws.len(), dcws as usize);
let combined = encode_rs_blocks(&data_cws, &layout);
Ok((idx, combined))
}
pub(crate) fn alloc_pixs(size: usize) -> Vec<i8> {
vec![-1; size * size]
}
pub(crate) fn place_corner_finders(pixs: &mut [i8], size: usize) {
let idx = |x: usize, y: usize| y * size + x;
for y in 0..8 {
for x in 0..8 {
let fp = FPAT[y][x] as i8;
let fp2 = FPAT2[y][x] as i8;
pixs[idx(x, y)] = fp;
pixs[idx(size - 1 - x, y)] = fp;
pixs[idx(size - 1 - x, size - 1 - y)] = fp;
pixs[idx(x, size - 1 - y)] = fp2;
}
}
}
fn trmv(x: usize, y: usize, size: usize) -> usize {
y * size + (size - 1 - x)
}
fn aplot(pixs: &mut [i8], x: usize, y: usize, val: i8, size: usize) {
pixs[trmv(x, y, size)] = val;
pixs[trmv(y, x, size)] = val;
}
pub(crate) fn place_alignment_patterns(pixs: &mut [i8], metric: &HanXinMetric, size: usize) {
if metric.alig_count == 0 {
return;
}
let alnk = metric.alig_step as usize;
let alnn = metric.alig_count as usize;
let alnr = size - alnk * alnn;
let mut i = 0usize;
let mut stag = 0usize;
loop {
if i >= size {
break;
}
for j in 0..size {
let cond = if j + alnr < size {
((j / alnk + stag) % 2 == 0 && !(i == 0 && j < alnk)) || (j % alnk == 0)
} else {
(alnn + stag) % 2 == 0
};
if cond {
aplot(pixs, j, i, 1, size);
if i + 1 < size && j + 1 < size {
aplot(pixs, j + 1, i + 1, 0, size);
}
}
}
if i + alnr == size {
i = i + alnr - 1;
} else {
i += alnk;
}
stag = 1 - stag;
}
let mut i = alnk;
while i <= size.saturating_sub(2) {
if (i / alnk) % 2 != 0 {
pixs[trmv(0, i - 1, size)] = 0;
pixs[trmv(0, i + 1, size)] = 0;
pixs[trmv(1, i - 1, size)] = 0;
pixs[trmv(1, i, size)] = 0;
pixs[trmv(1, i + 1, size)] = 0;
pixs[trmv(i - 1, 0, size)] = 0;
pixs[trmv(i + 1, 0, size)] = 0;
pixs[trmv(i - 1, 1, size)] = 0;
pixs[trmv(i, 1, size)] = 0;
pixs[trmv(i + 1, 1, size)] = 0;
}
if pixs[trmv(size - 1, i - 1, size)] != 1 {
pixs[trmv(size - 1, i - 1, size)] = 0;
pixs[trmv(size - 2, i - 1, size)] = 0;
pixs[trmv(size - 2, i, size)] = 0;
pixs[trmv(size - 2, i + 1, size)] = 0;
pixs[trmv(size - 1, i + 1, size)] = 0;
pixs[trmv(i - 1, size - 1, size)] = 0;
pixs[trmv(i - 1, size - 2, size)] = 0;
pixs[trmv(i, size - 2, size)] = 0;
pixs[trmv(i + 1, size - 2, size)] = 0;
pixs[trmv(i + 1, size - 1, size)] = 0;
}
i += alnk;
}
}
pub(crate) fn function_info_cells(size: usize) -> Vec<(usize, usize)> {
let mut cells = Vec::with_capacity(68);
for x in 0..=8 {
cells.push((x, 8));
cells.push((size - 1 - x, size - 9));
}
for y in 0..=7 {
cells.push((8, 7 - y));
cells.push((size - 9, size - 8 + y));
}
for y in 0..=8 {
cells.push((size - 9, y));
cells.push((8, size - 1 - y));
}
for x in 0..=7 {
cells.push((size - 8 + x, 8));
cells.push((7 - x, size - 9));
}
cells
}
pub(crate) fn zero_function_info_cells(pixs: &mut [i8], size: usize) {
for (x, y) in function_info_cells(size) {
pixs[y * size + x] = 0;
}
}
pub(crate) fn place_data(pixs: &mut [i8], cws: &[u8], size: usize) {
let mut num: usize = 0;
for posy in 0..size {
for posx in 0..size {
let idx = posy * size + posx;
if pixs[idx] == -1 {
let cw_idx = num / 8;
let bit_idx = 7 - (num % 8);
let bit = if cw_idx < cws.len() {
(cws[cw_idx] >> bit_idx) & 1
} else {
0
};
pixs[idx] = bit as i8;
num += 1;
}
}
}
}
pub(crate) fn mask_value(m: u8, col: usize, row: usize) -> u8 {
match m {
0 => 1,
1 => ((col + row) % 2) as u8,
2 => (((row + col) % 3 + col % 3) % 2) as u8,
3 => {
((col % row + (row % col + (row % 3 + col % 3))) % 2) as u8
}
_ => panic!("hanxin mask {} out of range (must be 0..=3)", m),
}
}
pub(crate) fn build_mask_grid(pixs: &[i8], m: u8, size: usize) -> Vec<u8> {
let mut mask = vec![0u8; size * size];
for j in 0..size {
for i in 0..size {
let r = mask_value(m, i + 1, j + 1);
let idx = i + j * size;
mask[idx] = if r == 0 && pixs[idx] == -1 { 1 } else { 0 };
}
}
mask
}
pub(crate) fn apply_mask(pixs: &mut [i8], mask: &[u8]) {
debug_assert_eq!(pixs.len(), mask.len());
for (p, &m) in pixs.iter_mut().zip(mask.iter()) {
if m == 1 {
*p ^= 1;
}
}
}
pub(crate) fn build_function_info_bits(version_index: usize, eclevel: u8, mask: u8) -> Vec<u8> {
let m = &HANXIN_METRICS[version_index];
let size = m.size as u32;
let funval = (((((size - 21) / 2) + 20) * 4) + u32::from(eclevel - 1)) * 4 + u32::from(mask);
let fundata: [u8; 3] = [
((funval >> 8) & 0xF) as u8,
((funval >> 4) & 0xF) as u8,
(funval & 0xF) as u8,
];
let funecc = rs_encode_gf16(&fundata, 4);
let mut bits = Vec::with_capacity(34);
for &nib in &fundata {
for bit in (0..4).rev() {
bits.push((nib >> bit) & 1);
}
}
for &nib in &funecc {
for bit in (0..4).rev() {
bits.push((nib >> bit) & 1);
}
}
bits.extend_from_slice(&[0, 1, 0, 1, 0, 1]);
debug_assert_eq!(bits.len(), 34);
bits
}
pub(crate) fn place_function_info(pixs: &mut [i8], bits: &[u8], size: usize) {
debug_assert_eq!(bits.len(), 34);
let cells = function_info_cells(size);
for (i, &bit) in bits.iter().enumerate() {
pixs[cells[2 * i].1 * size + cells[2 * i].0] = bit as i8;
pixs[cells[2 * i + 1].1 * size + cells[2 * i + 1].0] = bit as i8;
}
}
pub(crate) fn encode_with_mask(
data: &[u8],
eclevel: u8,
mask: u8,
) -> Result<(Vec<i8>, usize), crate::error::Error> {
if !(1..=4).contains(&eclevel) {
return Err(crate::error::Error::InvalidData(format!(
"Han Xin Code: eclevel must be 1..=4, got {eclevel}",
)));
}
if mask > 3 {
return Err(crate::error::Error::InvalidData(format!(
"Han Xin Code: mask must be 0..=3, got {mask}",
)));
}
let (version_index, cws) = encode_final_codewords(data, eclevel)?;
let m = &HANXIN_METRICS[version_index];
let size = m.size as usize;
let mut pixs = alloc_pixs(size);
place_alignment_patterns(&mut pixs, m, size);
place_corner_finders(&mut pixs, size);
zero_function_info_cells(&mut pixs, size);
let mask_grid = build_mask_grid(&pixs, mask, size);
place_data(&mut pixs, &cws, size);
apply_mask(&mut pixs, &mask_grid);
let funbits = build_function_info_bits(version_index, eclevel, mask);
place_function_info(&mut pixs, &funbits, size);
Ok((pixs, version_index))
}
pub(crate) fn rle_cells<I: Iterator<Item = i8>>(cells: I) -> Vec<u32> {
let mut out: Vec<u32> = vec![0];
let mut prev: i8 = 0;
let mut first = true;
for c in cells {
if first {
first = false;
if c == prev {
out[0] += 1;
} else {
out.push(1);
prev = c;
}
} else if c == prev {
if let Some(run) = out.last_mut() {
*run += 1;
}
} else {
out.push(1);
prev = c;
}
}
if first {
return Vec::new();
}
out
}
pub(crate) fn evalfulln1n3(scrle: &[u32]) -> (u32, u32) {
let mut scr1: u32 = 0;
for &r in scrle {
if r >= 3 {
scr1 += 4 * r;
}
}
let mut scr3: u32 = 0;
let len = scrle.len();
let mut j = 5usize;
while j < len {
let val = scrle[j];
if val % 3 == 0 {
let fact = val / 3;
let window_ok = scrle[j - 4..j].iter().all(|&v| v == fact);
let edge_or_neighbor_run =
j == 5 || j + 2 >= len || scrle[j - 5] >= 3 || scrle[j + 1] >= 3;
if window_ok && edge_or_neighbor_run {
scr3 += 50;
}
}
j += 2;
}
if len >= 5 {
let mut j = 1usize;
let limit = len - 5;
while j <= limit {
let val = scrle[j];
if val % 3 == 0 {
let fact = val / 3;
let window_ok = scrle[j + 1..=j + 4].iter().all(|&v| v == fact);
let edge_or_neighbor_run =
j == 1 || j + 6 >= len || scrle[j - 1] >= 3 || scrle[j + 5] >= 3;
if window_ok && edge_or_neighbor_run {
scr3 += 50;
}
}
j += 2;
}
}
(scr1, scr3)
}
pub(crate) fn evalfull(sym: &[i8], size: usize) -> u32 {
debug_assert_eq!(sym.len(), size * size);
let mut n1: u32 = 0;
let mut n3: u32 = 0;
for i in 0..size {
let column = (0..size).map(|y| sym[y * size + i]);
let scrle = rle_cells(column);
let (s1, s3) = evalfulln1n3(&scrle);
n1 += s1;
n3 += s3;
}
for i in 0..size {
let row_iter = sym[i * size..(i + 1) * size].iter().copied();
let scrle = rle_cells(row_iter);
let (s1, s3) = evalfulln1n3(&scrle);
n1 += s1;
n3 += s3;
}
n1 + n3
}
fn compute_masked_symbol(pixs: &[i8], mask_grid: &[u8]) -> Vec<i8> {
debug_assert_eq!(pixs.len(), mask_grid.len());
pixs.iter()
.zip(mask_grid.iter())
.map(|(&p, &m)| if m == 1 { p ^ 1 } else { p })
.collect()
}
pub(crate) fn pick_best_mask(pixs_with_data: &[i8], size: usize) -> u8 {
let mut best_mask: u8 = 0;
let mut best_score: u32 = u32::MAX;
for m in 0u8..=3 {
let grid = build_mask_grid_data_zone_only(pixs_with_data, m, size);
let masked = compute_masked_symbol(pixs_with_data, &grid);
let score = evalfull(&masked, size);
if score < best_score {
best_score = score;
best_mask = m;
}
}
best_mask
}
fn build_mask_grid_data_zone_only(pixs_with_data: &[i8], m: u8, size: usize) -> Vec<u8> {
let _ = pixs_with_data;
let mut scratch = alloc_pixs(size);
let version_index = (size - 23) / 2;
let metric = &HANXIN_METRICS[version_index];
place_alignment_patterns(&mut scratch, metric, size);
place_corner_finders(&mut scratch, size);
zero_function_info_cells(&mut scratch, size);
let mut mask = vec![0u8; size * size];
for j in 0..size {
for i in 0..size {
let idx = i + j * size;
let is_data = scratch[idx] == -1;
let mv = mask_value(m, i + 1, j + 1);
mask[idx] = if mv == 0 && is_data { 1 } else { 0 };
}
}
mask
}
pub(crate) fn encode_auto(
data: &[u8],
eclevel: u8,
) -> Result<(Vec<i8>, usize, u8), crate::error::Error> {
if !(1..=4).contains(&eclevel) {
return Err(crate::error::Error::InvalidData(format!(
"Han Xin Code: eclevel must be 1..=4, got {eclevel}",
)));
}
let (version_index, cws) = encode_final_codewords(data, eclevel)?;
let m = &HANXIN_METRICS[version_index];
let size = m.size as usize;
let mut pixs = alloc_pixs(size);
place_alignment_patterns(&mut pixs, m, size);
place_corner_finders(&mut pixs, size);
zero_function_info_cells(&mut pixs, size);
place_data(&mut pixs, &cws, size);
let best_mask = pick_best_mask(&pixs, size);
let mask_grid = build_mask_grid_data_zone_only(&pixs, best_mask, size);
apply_mask(&mut pixs, &mask_grid);
let funbits = build_function_info_bits(version_index, eclevel, best_mask);
place_function_info(&mut pixs, &funbits, size);
Ok((pixs, version_index, best_mask))
}
pub(crate) fn interleave_codewords(cws: &[u8], rbit: u32) -> Vec<u8> {
let ncws = cws.len();
let max_k = (ncws.saturating_sub(1)).min(12);
let mut out: Vec<u8> = Vec::with_capacity(ncws + if rbit > 0 { 1 } else { 0 });
for k in 0..=max_k {
let mut i = k;
while i < ncws {
out.push(cws[i]);
i += 13;
}
}
debug_assert_eq!(out.len(), ncws);
if rbit > 0 {
out.push(0);
}
out
}
pub(crate) fn encode_final_codewords(
data: &[u8],
eclevel: u8,
) -> Result<(usize, Vec<u8>), crate::error::Error> {
let (idx, combined) = encode_codewords(data, eclevel)?;
let rbit = HANXIN_METRICS[idx].cap_bits % 8;
Ok((idx, interleave_codewords(&combined, rbit)))
}
fn extras_lookup<'a>(opts: &'a crate::options::Options, key: &str) -> Option<&'a str> {
opts.extras
.iter()
.find_map(|(k, v)| if k == key { Some(v.as_str()) } else { None })
}
fn parse_eclevel(opts: &crate::options::Options) -> Result<u8, crate::error::Error> {
let raw = extras_lookup(opts, "eclevel").unwrap_or("L1");
match raw.trim().to_ascii_uppercase().as_str() {
"L1" | "1" => Ok(1),
"L2" | "2" => Ok(2),
"L3" | "3" => Ok(3),
"L4" | "4" => Ok(4),
_ => Err(crate::error::Error::InvalidData(format!(
"Han Xin Code: invalid eclevel {raw:?}; expected L1..L4",
))),
}
}
fn parse_mask_opt(opts: &crate::options::Options) -> Result<Option<u8>, crate::error::Error> {
match extras_lookup(opts, "mask") {
None => Ok(None),
Some(raw) => match raw.trim() {
"0" => Ok(Some(0)),
"1" => Ok(Some(1)),
"2" => Ok(Some(2)),
"3" => Ok(Some(3)),
_ => Err(crate::error::Error::InvalidData(format!(
"Han Xin Code: invalid mask {raw:?}; expected 0..=3",
))),
},
}
}
pub fn encode_symbology(
data: &str,
opts: &crate::options::Options,
) -> Result<crate::encoding::BitMatrix, crate::error::Error> {
let eclevel = parse_eclevel(opts)?;
let (pixs, version_index) = match parse_mask_opt(opts)? {
Some(mask) => encode_with_mask(data.as_bytes(), eclevel, mask)?,
None => {
let (p, v, _best) = encode_auto(data.as_bytes(), eclevel)?;
(p, v)
}
};
let size = HANXIN_METRICS[version_index].size as usize;
let mut bm = crate::encoding::BitMatrix::new(size, size);
for y in 0..size {
for x in 0..size {
if pixs[y * size + x] == 1 {
bm.set(x, y, true);
}
}
}
Ok(bm)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fpat_is_8x8() {
assert_eq!(FPAT.len(), 8);
for row in &FPAT {
assert_eq!(row.len(), 8);
}
}
#[test]
fn fpat2_is_8x8() {
assert_eq!(FPAT2.len(), 8);
for row in &FPAT2 {
assert_eq!(row.len(), 8);
}
}
#[test]
fn fpat_first_row_matches_bwipp() {
assert_eq!(FPAT[0], [1, 1, 1, 1, 1, 1, 1, 0]);
}
#[test]
fn fpat2_first_row_matches_bwipp() {
assert_eq!(FPAT2[0], [1, 1, 1, 0, 1, 0, 1, 0]);
}
#[test]
fn encode_symbology_round_trips_via_options() {
let mut opts = crate::options::Options::default();
opts.extras.push(("mask".into(), "0".into()));
let bm = encode_symbology("A", &opts).expect(
"encode_symbology(\"A\", mask=0) (Han Xin V1 23×23, explicit mask=0) must succeed",
);
assert_eq!(bm.width(), 23);
assert_eq!(bm.height(), 23);
let (pixs, _) = encode_with_mask(b"A", 1, 0).expect(
"encode_with_mask(b\"A\", eclevel=1, mask=0) (parallel reference path for BitMatrix cross-check) must succeed",
);
for y in 0..23 {
for x in 0..23 {
let expected = pixs[y * 23 + x] == 1;
assert_eq!(bm.get(x, y), expected, "BitMatrix cell ({x},{y}) mismatch",);
}
}
}
#[test]
fn encode_symbology_picks_eclevel_extra() {
let mut opts = crate::options::Options::default();
opts.extras.push(("eclevel".into(), "L2".into()));
opts.extras.push(("mask".into(), "2".into()));
let bm = encode_symbology("HELLO", &opts).expect(
"encode_symbology(\"HELLO\", eclevel=L2, mask=2) (extras thread through to encode_with_mask) must succeed",
);
let (pixs, _) = encode_with_mask(b"HELLO", 2, 2).expect(
"encode_with_mask(b\"HELLO\", eclevel=2, mask=2) (parallel reference path for BitMatrix cross-check) must succeed",
);
let size = 23usize;
for y in 0..size {
for x in 0..size {
let expected = pixs[y * size + x] == 1;
assert_eq!(bm.get(x, y), expected, "({x},{y}) mismatch");
}
}
}
#[test]
fn encode_symbology_rejects_bad_eclevel() {
let mut opts = crate::options::Options::default();
opts.extras.push(("eclevel".into(), "L9".into()));
let msg = match encode_symbology("A", &opts).unwrap_err() {
crate::error::Error::InvalidData(m) => m,
err => panic!(
"encode_symbology(\"A\", eclevel=\"L9\") must reject as Err(InvalidData(parse_eclevel)); got {err:?}"
),
};
assert!(
msg.contains("Han Xin Code:"),
"missing `Han Xin Code:` symbology prefix: {msg:?}"
);
assert!(
msg.contains("invalid eclevel"),
"missing `invalid eclevel` predicate: {msg:?}"
);
assert!(
msg.contains("\"L9\""),
"missing `\"L9\"` raw-Debug echo: {msg:?}"
);
assert!(
msg.contains("L1..L4"),
"missing `L1..L4` valid-range hint: {msg:?}"
);
assert!(
!msg.contains("invalid mask") && !msg.contains("0..=3"),
"cross-arm contamination — bad-eclevel reject leaked mask-arm diagnostic: {msg:?}"
);
}
#[test]
fn encode_symbology_rejects_bad_mask() {
let mut opts = crate::options::Options::default();
opts.extras.push(("mask".into(), "9".into()));
let msg = match encode_symbology("A", &opts).unwrap_err() {
crate::error::Error::InvalidData(m) => m,
err => panic!(
"encode_symbology(\"A\", mask=\"9\") must reject as Err(InvalidData(parse_mask_opt)); got {err:?}"
),
};
assert!(
msg.contains("Han Xin Code:"),
"missing `Han Xin Code:` symbology prefix: {msg:?}"
);
assert!(
msg.contains("invalid mask"),
"missing `invalid mask` predicate: {msg:?}"
);
assert!(
msg.contains("\"9\""),
"missing `\"9\"` raw-Debug echo: {msg:?}"
);
assert!(
msg.contains("0..=3"),
"missing `0..=3` valid-range hint: {msg:?}"
);
assert!(
!msg.contains("invalid eclevel") && !msg.contains("L1..L4"),
"cross-arm contamination — bad-mask reject leaked eclevel-arm diagnostic: {msg:?}"
);
}
#[test]
fn evalfull_scores_match_bwip_js_a_l1() {
let mut pixs = alloc_pixs(23);
place_alignment_patterns(&mut pixs, &HANXIN_METRICS[0], 23);
place_corner_finders(&mut pixs, 23);
zero_function_info_cells(&mut pixs, 23);
let (idx, cws) = encode_final_codewords(b"A", 1).expect(
"encode_final_codewords(b\"A\", L1) (Han Xin V1 L1 codeword build for mask-scoring evalfull oracle) must succeed",
);
assert_eq!(idx, 0);
place_data(&mut pixs, &cws, 23);
let want: [u32; 4] = [4628, 3474, 3644, 3966];
for (m, w) in want.iter().enumerate() {
let grid = build_mask_grid_data_zone_only(&pixs, m as u8, 23);
let masked = compute_masked_symbol(&pixs, &grid);
let s = evalfull(&masked, 23);
assert_eq!(s, *w, "mask {m}: got {s} want {w}");
}
let best = pick_best_mask(&pixs, 23);
assert_eq!(best, 1);
}
#[test]
fn evalfull_scores_match_bwip_js_hello_l2() {
let mut pixs = alloc_pixs(23);
place_alignment_patterns(&mut pixs, &HANXIN_METRICS[0], 23);
place_corner_finders(&mut pixs, 23);
zero_function_info_cells(&mut pixs, 23);
let (idx, cws) = encode_final_codewords(b"HELLO", 2).expect(
"encode_final_codewords(b\"HELLO\", L2) (Han Xin V1 L2 codeword build for evalfull oracle, 5-char payload) must succeed",
);
assert_eq!(idx, 0);
place_data(&mut pixs, &cws, 23);
let want: [u32; 4] = [4156, 3818, 3852, 3964];
for (m, w) in want.iter().enumerate() {
let grid = build_mask_grid_data_zone_only(&pixs, m as u8, 23);
let masked = compute_masked_symbol(&pixs, &grid);
let s = evalfull(&masked, 23);
assert_eq!(s, *w, "mask {m}: got {s} want {w}");
}
let best = pick_best_mask(&pixs, 23);
assert_eq!(best, 1);
}
#[test]
fn encode_auto_matches_explicit_mask_for_best_choice() {
let (auto_pixs, _, best_mask) = encode_auto(b"A", 1).expect(
"encode_auto(b\"A\", L1) (Han Xin V1 L1 auto-pick path; selects best mask via evalfull scoring) must succeed",
);
let (explicit_pixs, _) = encode_with_mask(b"A", 1, best_mask).expect(
"encode_with_mask(b\"A\", L1, best_mask) (Han Xin V1 L1 explicit-mask path; must reproduce auto-pick pixs) must succeed",
);
assert_eq!(best_mask, 1);
assert_eq!(auto_pixs, explicit_pixs);
}
#[test]
fn encode_auto_matches_bwip_js_a_l1_full_pixs() {
#[rustfmt::skip]
let want: [u8; 529] = [
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,
1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1,
1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1,
1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
];
let (pixs, _, best_mask) = encode_auto(b"A", 1).expect(
"encode_auto(b\"A\", L1) (Han Xin V1 L1 end-to-end auto-pick path feeding 529-byte bwip-js pixs golden) must succeed",
);
assert_eq!(best_mask, 1, "auto-pick must select mask 1 for 'A' L1");
let got: Vec<u8> = pixs.iter().map(|&v| v as u8).collect();
assert_eq!(
got,
want.to_vec(),
"Han Xin auto pixs must match bwip-js byte-for-byte (A, L1, auto-mask)",
);
}
#[test]
fn encode_symbology_auto_picks_best_mask_when_extras_omit_mask() {
let mut opts = crate::options::Options::default();
opts.extras.push(("eclevel".into(), "L1".into()));
let bm = encode_symbology("A", &opts).expect(
"encode_symbology(\"A\", eclevel=L1 only) (Han Xin Symbology dispatch path; no `mask` extra → auto-pick via encode_auto) must succeed",
);
let (auto_pixs, _, _) = encode_auto(b"A", 1).expect(
"encode_auto(b\"A\", L1) (Han Xin V1 L1 auto-pick reference for Symbology cross-check) must succeed",
);
for y in 0..23 {
for x in 0..23 {
let expected = auto_pixs[y * 23 + x] == 1;
assert_eq!(bm.get(x, y), expected, "({x},{y})");
}
}
}
#[test]
fn evalfull_scores_match_bwip_js_corpus() {
#[allow(clippy::type_complexity)]
let corpus: &[(&[u8], u8, [u32; 4], u8, usize)] = &[
(b"A", 1, [4628, 3474, 3644, 3966], 1, 23),
(b"A", 2, [4396, 3864, 3752, 3968], 2, 23),
(b"A", 3, [4180, 3820, 3884, 3838], 1, 23),
(b"A", 4, [3956, 3832, 4024, 3868], 1, 23),
(b"B", 1, [4548, 3430, 3596, 3908], 1, 23),
(b"B", 2, [4376, 3820, 3728, 3936], 2, 23),
(b"B", 3, [4256, 3806, 3768, 3772], 2, 23),
(b"B", 4, [4176, 3810, 3816, 3864], 1, 23),
(b"C", 1, [4576, 3416, 3708, 3984], 1, 23),
(b"C", 2, [4400, 3682, 3728, 3840], 1, 23),
(b"C", 3, [4212, 3770, 3954, 3910], 1, 23),
(b"C", 4, [4084, 3764, 3960, 3962], 1, 23),
(b"1", 1, [4588, 3468, 3700, 4050], 1, 23),
(b"1", 2, [4532, 3754, 3716, 3988], 2, 23),
(b"1", 3, [4324, 3692, 3770, 3856], 1, 23),
(b"1", 4, [4084, 3752, 3800, 3966], 1, 23),
(b"Hello World", 1, [4120, 3884, 3892, 3840], 3, 23),
(b"Hello World", 2, [4064, 3888, 4010, 3916], 1, 23),
(b"Hello World", 3, [4454, 4388, 4248, 4450], 2, 25),
(b"Hello World", 4, [4338, 4206, 4272, 4232], 1, 25),
(b"ABC123", 1, [4388, 3746, 3776, 3922], 1, 23),
(b"ABC123", 2, [4352, 3986, 3860, 3928], 2, 23),
(b"ABC123", 3, [4000, 3810, 3768, 3802], 2, 23),
(b"ABC123", 4, [3996, 3754, 4054, 3920], 1, 23),
];
for (data, eclevel, scores, expected_best, expected_size) in corpus {
let data_echo = std::str::from_utf8(data).unwrap_or("<non-utf8>");
let (version_index, cws) = encode_final_codewords(data, *eclevel).unwrap_or_else(|e| {
panic!(
"encode_final_codewords({data_echo:?}, L{eclevel}) (Han Xin evalfull corpus row: codeword build feeding 4-mask evalfull oracle + pick_best_mask) must succeed: {e:?}",
)
});
let m = &HANXIN_METRICS[version_index];
let size = m.size as usize;
assert_eq!(size, *expected_size, "version size for {data:?} L{eclevel}",);
let mut pixs = alloc_pixs(size);
place_alignment_patterns(&mut pixs, m, size);
place_corner_finders(&mut pixs, size);
zero_function_info_cells(&mut pixs, size);
place_data(&mut pixs, &cws, size);
for (mi, w) in scores.iter().enumerate() {
let grid = build_mask_grid_data_zone_only(&pixs, mi as u8, size);
let masked = compute_masked_symbol(&pixs, &grid);
let s = evalfull(&masked, size);
assert_eq!(
s, *w,
"score mismatch: {data:?} L{eclevel} mask={mi}: got {s} want {w}",
);
}
let best = pick_best_mask(&pixs, size);
assert_eq!(
best, *expected_best,
"best mask: {data:?} L{eclevel}: got {best} want {expected_best}",
);
}
}
#[test]
fn rle_cells_basic() {
let v: Vec<i8> = vec![0, 0, 0, 1, 1, 0, 0, 0, 0];
assert_eq!(rle_cells(v.into_iter()), vec![3, 2, 4]);
let empty: Vec<i8> = vec![];
assert_eq!(rle_cells(empty.into_iter()), Vec::<u32>::new());
let all_ones: Vec<i8> = vec![1; 10];
assert_eq!(rle_cells(all_ones.into_iter()), vec![0, 10]);
let all_zeros: Vec<i8> = vec![0; 10];
assert_eq!(rle_cells(all_zeros.into_iter()), vec![10]);
let trace: Vec<i8> = vec![1, 1, 0];
assert_eq!(rle_cells(trace.into_iter()), vec![0, 2, 1]);
}
#[test]
fn evalfulln1n3_n1_basic() {
let (n1, n3) = evalfulln1n3(&[3, 1, 4, 2, 5]);
assert_eq!(n1, 48);
assert_eq!(n3, 0);
}
#[test]
fn evalfulln1n3_n3_pre_block_pattern() {
let (_, n3) = evalfulln1n3(&[1, 1, 1, 1, 1, 3]);
assert_eq!(n3, 50);
}
#[test]
fn noblk_sentinel_matches_bwipp() {
assert_eq!(NOBLK, None);
}
#[test]
fn hanxin_metrics_table_has_84_rows() {
assert_eq!(HANXIN_METRICS.len(), 84);
}
#[test]
fn hanxin_metrics_version_1_matches_bwipp() {
let m = &HANXIN_METRICS[0];
assert_eq!(m.version, "1");
assert_eq!(m.size, 23);
assert_eq!(m.alig_step, -1);
assert_eq!(m.alig_count, 0);
assert_eq!(m.cap_bits, 205);
assert_eq!(m.blocks[0], [Some((1, 21, 4)), None, None]);
assert_eq!(m.blocks[1], [Some((1, 17, 8)), None, None]);
assert_eq!(m.blocks[2], [Some((1, 13, 12)), None, None]);
assert_eq!(m.blocks[3], [Some((1, 9, 16)), None, None]);
}
#[test]
fn hanxin_metrics_version_84_matches_bwipp() {
let m = &HANXIN_METRICS[83];
assert_eq!(m.version, "84");
assert_eq!(m.size, 189);
assert_eq!(m.alig_step, 17);
assert_eq!(m.alig_count, 10);
assert_eq!(m.cap_bits, 31091);
assert_eq!(m.blocks[0], [Some((30, 105, 20)), Some((1, 114, 22)), None]);
assert_eq!(m.blocks[3], [Some((79, 18, 28)), Some((4, 33, 30)), None]);
}
#[test]
fn hanxin_metrics_versions_are_consecutive() {
for (i, m) in HANXIN_METRICS.iter().enumerate() {
let expected = (i + 1).to_string();
assert_eq!(m.version, expected, "metric[{i}] version mismatch");
}
}
#[test]
fn hanxin_metrics_size_is_odd_and_increasing() {
let mut prev_size = 0u8;
for m in HANXIN_METRICS.iter() {
assert!(
m.size % 2 == 1,
"version {} size {} not odd",
m.version,
m.size
);
assert!(
m.size > prev_size,
"version {} size {} not increasing",
m.version,
m.size
);
prev_size = m.size;
}
}
#[test]
fn rs_encode_gf256_matches_oracle() {
let data: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
assert_eq!(rs_encode_gf256(&data, 4), vec![37, 64, 157, 114]);
assert_eq!(
rs_encode_gf256(&data, 8),
vec![243, 159, 51, 120, 28, 244, 160, 172],
);
let data2: [u8; 8] = [0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE];
assert_eq!(
rs_encode_gf256(&data2, 8),
vec![192, 221, 74, 236, 166, 89, 3, 128],
);
}
#[test]
fn rs_encode_gf16_matches_bwip_js_funecc_v1_l1_m0() {
let funecc = rs_encode_gf16(&[1, 5, 0], 4);
assert_eq!(funecc, vec![8, 15, 4, 12]);
}
#[test]
fn rs_encode_gf16_basic_smoke() {
let data: [u8; 4] = [1, 2, 3, 4];
let r = rs_encode_gf16(&data, 4);
assert_eq!(r.len(), 4);
for v in &r {
assert!(*v <= 15, "gf16 element must be 0..=15");
}
}
#[test]
fn gf256_hanxin_poly_is_355() {
assert_eq!(GF256_HANXIN.poly, 355);
assert_eq!(GF256_HANXIN.size, 256);
}
#[test]
fn pad_to_dmod_extends_with_zeros_only_below_target() {
let mut bits = vec![true, false];
pad_to_dmod(&mut bits, 5);
assert_eq!(bits, vec![true, false, false, false, false]);
let mut bits = vec![true, true];
pad_to_dmod(&mut bits, 2);
assert_eq!(bits, vec![true, true]);
let mut bits = vec![true; 5];
pad_to_dmod(&mut bits, 3);
assert_eq!(bits, vec![true; 5]);
let mut bits = vec![];
pad_to_dmod(&mut bits, 4);
assert_eq!(bits, vec![false, false, false, false]);
let mut bits = vec![];
pad_to_dmod(&mut bits, 0);
assert_eq!(bits, Vec::<bool>::new());
}
#[test]
fn encode_binary_bits_layout() {
let bits = encode_binary_bits(b"A").expect(
"encode_binary_bits(b\"A\") (Han Xin Binary mode: 4-bit mode 0b0011 + 13-bit length + 8-bit body) must succeed",
);
assert_eq!(bits.len(), 25);
assert_eq!(&bits[..4], &[false, false, true, true]);
let mut len_val = 0u16;
for &b in &bits[4..17] {
len_val = (len_val << 1) | u16::from(b);
}
assert_eq!(len_val, 1);
let mut byte_val = 0u8;
for &b in &bits[17..25] {
byte_val = (byte_val << 1) | u8::from(b);
}
assert_eq!(byte_val, 0x41);
}
#[test]
fn encode_binary_bits_rejects_oversize() {
let big = vec![0u8; 8192];
let err = encode_binary_bits(&big).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("oversize binary bits must yield InvalidData; got {err:?}");
};
assert!(
msg.contains("Han Xin Code:")
&& msg.contains("8192")
&& msg.contains("13-bit length field")
&& msg.contains("8191"),
"diagnostic must pin symbology tag + length echo + 13-bit field hint + max (8191); \
got {msg:?}"
);
}
#[test]
fn select_version_picks_smallest_fitting() {
let (idx, ncws, dcws, _) = select_version(25, 1).expect(
"select_version(25 bits, L1) (Han Xin version-selector smallest-fit path; expects V1 ncws=25 dcws=21) must succeed",
);
assert_eq!(idx, 0); assert_eq!(ncws, 25); assert_eq!(dcws, 21);
}
#[test]
fn select_version_l4_picks_larger_for_same_input() {
let (idx, _, dcws, _) = select_version(25, 4).expect(
"select_version(25 bits, L4) (Han Xin version-selector L4 path; expects V1 dcws=9 due to L4's higher ECC overhead) must succeed",
);
assert_eq!(idx, 0);
assert_eq!(dcws, 9);
}
#[test]
fn select_version_invalid_eclevel_and_boundary_fits() {
assert!(
select_version(25, 5).is_none(),
"eclevel=5 (level_idx=4) must return None"
);
assert!(
select_version(25, 100).is_none(),
"eclevel=100 (level_idx way past 4) must return None"
);
let (idx_168, _, dcws_168, _) =
select_version(168, 1).expect("168 bits L1 must fit V1 (boundary)");
assert_eq!(
idx_168, 0,
"168 bits L1 fits V1 exactly (bits_len <= dmod with dmod=168)"
);
assert_eq!(dcws_168, 21, "V1 L1 dcws = 21");
let (idx_169, _, dcws_169, _) = select_version(169, 1).expect("169 bits L1 must fit V2");
assert_eq!(idx_169, 1, "169 bits L1 exceeds V1's 168-bit cap → V2");
assert_eq!(dcws_169, 31, "V2 L1 dcws = 31");
let (idx_l4_72, _, dcws_l4_72, _) = select_version(72, 4).expect("72 bits L4 must fit V1");
assert_eq!(idx_l4_72, 0);
assert_eq!(dcws_l4_72, 9);
let (idx_l4_73, _, dcws_l4_73, _) = select_version(73, 4).expect("73 bits L4 must fit V2");
assert_eq!(idx_l4_73, 1, "73 bits L4 exceeds V1's 72-bit cap → V2");
assert_eq!(dcws_l4_73, 15, "V2 L4 dcws = 15");
assert!(
select_version(15000, 4).is_none(),
"15000 bits L4 exceeds all 84 versions (V84 L4 dmod = 12432) → None"
);
}
#[test]
fn bits_to_codewords_msb_first() {
let bits: Vec<bool> = [
false, true, false, false, false, false, false, true, true, false, false, false, false,
false, true, false,
]
.to_vec();
assert_eq!(bits_to_codewords(&bits), vec![0x41, 0x82]);
}
#[test]
fn encode_final_codewords_matches_bwip_js_for_a_l1() {
let want: [u8; 26] = [
48, 0, 0, 0, 160, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 0, 210, 0, 0, 0, 209, 0, 0,
];
let (idx, cws) = encode_final_codewords(b"A", 1).expect(
"encode_final_codewords(b\"A\", L1) (Han Xin V1 L1 end-to-end codeword pipeline: bits → pad → cws → RS-ECC → interleave; 26-element bwip-js oracle) must succeed",
);
assert_eq!(idx, 0); assert_eq!(cws, want.to_vec());
}
#[test]
fn encode_codewords_isolates_pre_interleave_pipeline_for_a_l1() {
let (idx, combined) =
encode_codewords(b"A", 1).expect("encode_codewords(\"A\", L1) must succeed");
assert_eq!(idx, 0, "V1 metric chosen for 25-bit input at L1");
assert_eq!(combined.len(), 25, "V1 ncws = 25");
let want_data: [u8; 21] = [
48, 0, 160, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
assert_eq!(
combined[..21],
want_data,
"data section pins bits→pad→cws sub-pipeline"
);
let want_ecc: [u8; 4] = [148, 210, 0, 209];
assert_eq!(
combined[21..],
want_ecc,
"ECC section pins rs_encode_gf256 + encode_rs_blocks ordering"
);
}
#[test]
fn alloc_pixs_fills_with_minus_one() {
let p = alloc_pixs(23);
assert_eq!(p.len(), 23 * 23);
assert!(p.iter().all(|&v| v == -1));
}
#[test]
fn place_corner_finders_v1_top_left_matches_fpat() {
let mut pixs = alloc_pixs(23);
place_corner_finders(&mut pixs, 23);
for y in 0..8 {
for x in 0..8 {
assert_eq!(pixs[y * 23 + x], FPAT[y][x] as i8, "TL ({x}, {y}) mismatch",);
}
}
}
#[test]
fn place_corner_finders_v1_bottom_left_uses_fpat2() {
let mut pixs = alloc_pixs(23);
place_corner_finders(&mut pixs, 23);
for (y, row) in FPAT2.iter().enumerate() {
for (x, &cell) in row.iter().enumerate() {
let row_y = 23 - 1 - y;
assert_eq!(
pixs[row_y * 23 + x],
cell as i8,
"BL ({x}, y={row_y}) should be FPAT2[{y}][{x}]",
);
}
}
}
#[test]
fn place_corner_finders_v1_top_right_and_bottom_right_use_fpat() {
let mut pixs = alloc_pixs(23);
place_corner_finders(&mut pixs, 23);
for (y, row) in FPAT.iter().enumerate() {
for (x, &cell) in row.iter().enumerate() {
let tr_x = 23 - 1 - x;
assert_eq!(pixs[y * 23 + tr_x], cell as i8, "TR ({tr_x}, {y}) mismatch",);
let br_x = 23 - 1 - x;
let br_y = 23 - 1 - y;
assert_eq!(
pixs[br_y * 23 + br_x],
cell as i8,
"BR ({br_x}, {br_y}) mismatch",
);
}
}
}
#[test]
fn place_corner_finders_matches_bwip_js_v1_bottom_left() {
let mut pixs = alloc_pixs(23);
place_corner_finders(&mut pixs, 23);
let row22: Vec<i8> = (0..8).map(|x| pixs[22 * 23 + x]).collect();
assert_eq!(row22, vec![1, 1, 1, 0, 1, 0, 1, 0]);
let row16: Vec<i8> = (0..8).map(|x| pixs[16 * 23 + x]).collect();
assert_eq!(row16, vec![1, 1, 1, 1, 1, 1, 1, 0]);
let row15: Vec<i8> = (0..8).map(|x| pixs[15 * 23 + x]).collect();
assert_eq!(row15, vec![0; 8]);
}
#[test]
fn place_corner_finders_v1_centre_remains_unwritten() {
let mut pixs = alloc_pixs(23);
place_corner_finders(&mut pixs, 23);
assert_eq!(pixs[11 * 23 + 11], -1);
}
#[test]
fn interleave_codewords_stride_13_and_trailing_zero() {
let cws13: Vec<u8> = (0..13).collect();
assert_eq!(
interleave_codewords(&cws13, 0),
cws13,
"ncws=13: identity ordering (no second stride step)"
);
let cws14: Vec<u8> = (0..14).collect();
assert_eq!(
interleave_codewords(&cws14, 0),
vec![0, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
);
let cws26: Vec<u8> = (0..26).collect();
let expected: Vec<u8> = (0..13).flat_map(|k| [k, k + 13]).collect();
assert_eq!(interleave_codewords(&cws26, 0), expected);
assert_eq!(interleave_codewords(&[1, 2, 3, 4], 0), vec![1, 2, 3, 4]);
assert_eq!(interleave_codewords(&[1, 2, 3], 1), vec![1, 2, 3, 0]);
assert_eq!(interleave_codewords(&[1, 2, 3], 7), vec![1, 2, 3, 0]);
assert_eq!(interleave_codewords(&[1, 2, 3], 0), vec![1, 2, 3]);
}
#[test]
fn trmv_and_aplot_mirror_and_symmetric_writes() {
assert_eq!(trmv(0, 0, 5), 4, "(0,0) → row 0, mirrored col 4");
assert_eq!(trmv(4, 0, 5), 0, "(4,0) → row 0, mirrored col 0");
assert_eq!(trmv(0, 4, 5), 24, "(0,4) → row 4, mirrored col 4");
assert_eq!(trmv(4, 4, 5), 20, "(4,4) → row 4, mirrored col 0");
assert_eq!(trmv(2, 2, 5), 12, "(2,2) → centre");
assert_eq!(trmv(1, 3, 5), 18, "(1,3) → row 3, mirrored col 3");
let mut pixs = vec![0i8; 25];
aplot(&mut pixs, 0, 4, 7, 5);
assert_eq!(pixs[24], 7, "aplot writes trmv(0,4)=24");
assert_eq!(pixs[0], 7, "aplot writes trmv(4,0)=0 (symmetric)");
assert_eq!(pixs[12], 0);
assert_eq!(pixs[20], 0);
}
#[test]
fn compute_masked_symbol_xors_only_where_mask_is_one() {
let pixs: Vec<i8> = vec![0, 1, 0, 1, -1, -1];
let mask: Vec<u8> = vec![0, 0, 1, 1, 0, 0];
let got = compute_masked_symbol(&pixs, &mask);
assert_eq!(
got,
vec![0, 1, 1, 0, -1, -1],
"m=0 → identity; m=1 → XOR with 1"
);
let pixs: Vec<i8> = vec![0, 1];
let mask: Vec<u8> = vec![2, 2];
assert_eq!(
compute_masked_symbol(&pixs, &mask),
vec![0, 1],
"m=2 (not equal to 1) → identity"
);
assert_eq!(compute_masked_symbol(&[], &[]), Vec::<i8>::new());
}
#[test]
fn mask_value_known_outputs() {
assert_eq!(mask_value(0, 1, 1), 1);
assert_eq!(mask_value(0, 5, 7), 1);
assert_eq!(mask_value(1, 1, 1), 0); assert_eq!(mask_value(1, 1, 2), 1); assert_eq!(mask_value(1, 7, 7), 0); assert_eq!(mask_value(2, 1, 1), 1);
assert_eq!(mask_value(2, 3, 3), 0);
assert_eq!(mask_value(2, 2, 4), 0);
assert_eq!(mask_value(2, 4, 2), 1);
assert_eq!(mask_value(3, 1, 1), 0);
}
#[test]
fn build_mask_grid_marks_only_data_cells() {
let size = 5;
let mut pixs = vec![-1i8; 25];
pixs[0] = 1; pixs[24] = 0; let mask = build_mask_grid(&pixs, 1, size);
assert_eq!(mask[0], 0);
assert_eq!(mask[24], 0);
assert_eq!(mask[6], 1);
}
#[test]
fn apply_mask_flips_marked_cells() {
let mut pixs = vec![0i8, 1, 0, 1];
let mask = vec![0u8, 1, 1, 0];
apply_mask(&mut pixs, &mask);
assert_eq!(pixs, vec![0, 0, 1, 1]);
}
#[test]
fn place_data_fills_all_unwritten_cells() {
let size = 23;
let mut pixs = alloc_pixs(size);
place_corner_finders(&mut pixs, size);
let cws = vec![0xAA; 100]; place_data(&mut pixs, &cws, size);
assert!(!pixs.contains(&-1), "place_data must fill every -1 cell",);
assert!(pixs.iter().all(|&v| v == 0 || v == 1));
}
#[test]
fn place_data_alternates_with_aa_pattern() {
let size = 23;
let mut pixs = alloc_pixs(size);
place_corner_finders(&mut pixs, size);
let cws = vec![0xAA; 100];
place_data(&mut pixs, &cws, size);
assert_eq!(pixs[8], 1, "first data bit should be MSB of 0xAA = 1");
}
#[test]
fn build_function_info_bits_length_is_34() {
let bits = build_function_info_bits(0, 1, 0);
assert_eq!(bits.len(), 34);
for &b in &bits {
assert!(b == 0 || b == 1);
}
assert_eq!(&bits[28..], &[0, 1, 0, 1, 0, 1]);
}
#[test]
fn build_function_info_bits_v1_l1_m0_funval_336() {
let bits = build_function_info_bits(0, 1, 0);
assert_eq!(
&bits[..12],
&[0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0],
"fundata bits for v1 L1 m0",
);
}
#[test]
fn place_function_info_writes_at_funmap_cells() {
let size = 23;
let mut pixs = alloc_pixs(size);
let bits = build_function_info_bits(0, 1, 0);
place_function_info(&mut pixs, &bits, size);
assert_eq!(pixs[8 * size], bits[0] as i8);
assert_eq!(pixs[14 * size + 22], bits[0] as i8);
}
#[test]
fn function_info_cells_count_is_68() {
for size in [23, 25, 31, 41, 99, 189] {
let cells = function_info_cells(size);
assert_eq!(cells.len(), 68, "size {size} should have 68 cells");
}
}
#[test]
fn function_info_cells_v1_matches_oracle() {
let cells = function_info_cells(23);
assert_eq!(cells[0], (0, 8));
assert_eq!(cells[1], (22, 14));
assert_eq!(cells[2], (1, 8));
assert_eq!(cells[3], (21, 14));
assert_eq!(cells[64], (21, 8));
assert_eq!(cells[65], (1, 14));
assert_eq!(cells[66], (22, 8));
assert_eq!(cells[67], (0, 14));
}
#[test]
fn zero_function_info_cells_writes_zeros() {
let size = 23;
let mut pixs = alloc_pixs(size);
zero_function_info_cells(&mut pixs, size);
assert_eq!(pixs[8 * size], 0); assert_eq!(pixs[14 * size + 22], 0); assert_eq!(pixs[7 * size + 8], 0); assert_eq!(pixs[14], 0); assert_eq!(pixs[11 * size + 11], -1);
}
#[test]
fn place_alignment_patterns_v1_is_noop() {
let mut pixs = alloc_pixs(23);
place_alignment_patterns(&mut pixs, &HANXIN_METRICS[0], 23);
assert!(pixs.iter().all(|&v| v == -1));
}
#[test]
fn finders_plus_alignment_v5_corner_rows_match_bwip_js() {
let m = &HANXIN_METRICS[4];
assert_eq!(m.size, 31);
let size = m.size as usize;
let mut pixs = alloc_pixs(size);
place_alignment_patterns(&mut pixs, m, size);
place_corner_finders(&mut pixs, size);
let row0: Vec<i8> = (0..size).map(|x| pixs[x]).collect();
let want_row0: [i8; 31] = [
1, 1, 1, 1, 1, 1, 1, 0, -1, -1, -1, -1, -1, 0, 1, 0, -1, -1, -1, -1, -1, -1, -1, 0, 1,
1, 1, 1, 1, 1, 1,
];
assert_eq!(row0, want_row0.to_vec(), "row 0 mismatch");
let row7: Vec<i8> = (0..size).map(|x| pixs[7 * size + x]).collect();
let want_row7: [i8; 31] = [
0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0,
0, 0, 0, 0, 0, 0, 0,
];
assert_eq!(row7, want_row7.to_vec(), "row 7 mismatch");
}
#[test]
fn place_alignment_patterns_v4_writes_some_cells() {
let m = &HANXIN_METRICS[3];
assert_eq!(m.version, "4");
assert_eq!(m.size, 29);
assert_eq!(m.alig_step, 14);
assert_eq!(m.alig_count, 1);
let size = m.size as usize;
let mut pixs = alloc_pixs(size);
place_alignment_patterns(&mut pixs, m, size);
let written = pixs.iter().filter(|&&v| v != -1).count();
assert!(written > 0, "v4 should write some alignment cells");
}
fn alig_fingerprint(idx: usize) -> (usize, u64, usize, u64) {
let m = &HANXIN_METRICS[idx];
let size = m.size as usize;
let mut pixs = alloc_pixs(size);
place_alignment_patterns(&mut pixs, m, size);
let (mut c1, mut s1, mut c0, mut s0) = (0usize, 0u64, 0usize, 0u64);
for (k, &v) in pixs.iter().enumerate() {
if v == 1 {
c1 += 1;
s1 = s1.wrapping_add((k as u64).wrapping_mul(2_654_435_761));
} else if v == 0 {
c0 += 1;
s0 = s0.wrapping_add((k as u64).wrapping_mul(2_654_435_761));
}
}
(c1, s1, c0, s0)
}
#[test]
fn place_alignment_patterns_full_grid_pinned() {
let cases = [
(
4usize,
(64usize, 99461707964670u64, 38usize, 53513424941760u64),
),
(10, (143, 377158159537446, 131, 321972440066256)),
(15, (173, 690291328519572, 171, 660752767371164)), (20, (209, 1202549650548874, 183, 972309201511256)),
(41, (692, 10324693335985600, 637, 9187596762431464)),
];
for (idx, want) in cases {
assert_eq!(
alig_fingerprint(idx),
want,
"alignment fingerprint changed for HANXIN_METRICS[{idx}] (v{})",
HANXIN_METRICS[idx].version
);
}
}
#[test]
fn trmv_mirrors_horizontally() {
assert_eq!(trmv(0, 0, 23), 22);
assert_eq!(trmv(22, 0, 23), 0);
assert_eq!(trmv(5, 3, 23), 3 * 23 + 17);
}
#[test]
fn hanxin_metrics_capacity_matches_block_sum() {
for m in HANXIN_METRICS.iter() {
let ncws_expected = m.cap_bits / 8;
for (level_idx, level) in m.blocks.iter().enumerate() {
let sum: u32 = level
.iter()
.filter_map(|b| *b)
.map(|(c, d, e)| u32::from(c) * (u32::from(d) + u32::from(e)))
.sum();
assert_eq!(
sum, ncws_expected,
"version {} level {level_idx}: block sum {sum} != ncws {ncws_expected} (cap_bits={})",
m.version, m.cap_bits,
);
}
}
}
#[test]
fn encode_with_mask_rejects_invalid_eclevel() {
for bad in [0u8, 5, 99] {
let err = encode_with_mask(b"A", bad, 0).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("Han Xin Code:"),
"missing Han Xin Code prefix for eclevel={bad}: {msg:?}"
);
assert!(
msg.contains("eclevel must be 1..=4"),
"missing eclevel range predicate for eclevel={bad}: {msg:?}"
);
assert!(
msg.contains(&format!("got {bad}")),
"missing value echo `got {bad}`: {msg:?}"
);
assert!(
!msg.contains("mask must"),
"cross-arm contamination: eclevel reject mentions `mask must`: {msg:?}"
);
}
}
#[test]
fn encode_with_mask_rejects_invalid_mask() {
for bad in [4u8, 5, 200] {
let err = encode_with_mask(b"A", 1, bad).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("Han Xin Code:"),
"missing Han Xin Code prefix for mask={bad}: {msg:?}"
);
assert!(
msg.contains("mask must be 0..=3"),
"missing mask range predicate for mask={bad}: {msg:?}"
);
assert!(
msg.contains(&format!("got {bad}")),
"missing value echo `got {bad}`: {msg:?}"
);
assert!(
!msg.contains("eclevel must"),
"cross-arm contamination: mask reject mentions `eclevel must`: {msg:?}"
);
}
}
#[test]
fn encode_with_mask_produces_full_v1_grid() {
let (pixs, version_index) = encode_with_mask(b"A", 1, 0).expect(
"encode_with_mask(b\"A\", L1, mask=0) (Han Xin V1 L1 mask-0 smoke: full-grid fill, no -1 cells, no -1 residue) must succeed",
);
assert_eq!(version_index, 0, "input 'A' L1 should fit in v1");
let m = &HANXIN_METRICS[version_index];
let size = m.size as usize;
assert_eq!(size, 23);
assert_eq!(pixs.len(), size * size);
assert!(
!pixs.contains(&-1),
"encode_with_mask must leave no -1 cells",
);
assert!(
pixs.iter().all(|&v| v == 0 || v == 1),
"every cell must be 0 or 1",
);
}
#[test]
fn encode_with_mask_top_left_finder_intact() {
let (pixs, _) = encode_with_mask(b"A", 1, 0).expect(
"encode_with_mask(b\"A\", L1, mask=0) (Han Xin V1 L1 mask-0 TL-finder-intact: FPAT 8×8 must survive XOR) must succeed",
);
let size = 23usize;
for (y, row) in FPAT.iter().enumerate() {
for (x, &cell) in row.iter().enumerate() {
assert_eq!(
pixs[y * size + x],
cell as i8,
"TL finder cell ({x},{y}) must equal FPAT",
);
}
}
}
#[test]
fn encode_with_mask_varies_with_mask_choice() {
let (m0, v0) = encode_with_mask(b"A", 1, 0).expect(
"encode_with_mask(b\"A\", L1, mask=0) (Han Xin V1 L1 mask-0 path: identity mask, r=1 always) must succeed",
);
let (m1, v1) = encode_with_mask(b"A", 1, 1).expect(
"encode_with_mask(b\"A\", L1, mask=1) (Han Xin V1 L1 mask-1 path: (col+row) mod 2) must succeed",
);
let (m2, v2) = encode_with_mask(b"A", 1, 2).expect(
"encode_with_mask(b\"A\", L1, mask=2) (Han Xin V1 L1 mask-2 path: ((row+col) mod 3 + col mod 3) mod 2) must succeed",
);
let (m3, v3) = encode_with_mask(b"A", 1, 3).expect(
"encode_with_mask(b\"A\", L1, mask=3) (Han Xin V1 L1 mask-3 path: (col mod row + (row mod col + (row mod 3 + col mod 3))) mod 2) must succeed",
);
assert_eq!(v0, v1);
assert_eq!(v0, v2);
assert_eq!(v0, v3);
assert_ne!(m0, m1, "mask 0 and mask 1 must produce different grids");
assert_ne!(m0, m2, "mask 0 and mask 2 must produce different grids");
assert_ne!(m0, m3, "mask 0 and mask 3 must produce different grids");
}
#[test]
fn encode_with_mask_matches_bwip_js_v1_l1_m0() {
#[rustfmt::skip]
let want: [u8; 529] = [
1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1,
1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
];
let (pixs, version_index) = encode_with_mask(b"A", 1, 0).expect(
"encode_with_mask(b\"A\", L1, mask=0) (Han Xin V1 L1 mask-0 byte-for-byte 529-cell bwip-js oracle) must succeed",
);
assert_eq!(version_index, 0, "input 'A' L1 fits in v1");
assert_eq!(pixs.len(), 529);
let got: Vec<u8> = pixs.iter().map(|&v| v as u8).collect();
assert_eq!(
got,
want.to_vec(),
"Han Xin pixs must match bwip-js byte-for-byte for input 'A' L1 mask 0",
);
}
#[test]
fn encode_with_mask_matches_bwip_js_hello_l2_m0() {
#[rustfmt::skip]
let want: [u8; 529] = [
1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1,
1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,
0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1,
1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
];
let (pixs, version_index) = encode_with_mask(b"HELLO", 2, 0).expect(
"encode_with_mask(b\"HELLO\", L2, mask=0) (Han Xin V1 L2 mask-0 byte-for-byte 529-cell bwip-js oracle; 17 data + 8 ECC) must succeed",
);
assert_eq!(version_index, 0, "input 'HELLO' L2 fits in v1");
assert_eq!(pixs.len(), 529);
let got: Vec<u8> = pixs.iter().map(|&v| v as u8).collect();
assert_eq!(
got,
want.to_vec(),
"Han Xin pixs must match bwip-js byte-for-byte for input 'HELLO' L2 mask 0",
);
}
#[test]
fn encode_with_mask_matches_bwip_js_hello_l2_m2() {
#[rustfmt::skip]
let want: [u8; 529] = [
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1,
1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1,
0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,
1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
];
let (pixs, _) = encode_with_mask(b"HELLO", 2, 2).expect(
"encode_with_mask(b\"HELLO\", L2, mask=2) (Han Xin V1 L2 mask-2 byte-for-byte oracle; guards against row/col swap in ((row+col)%3+col%3)%2) must succeed",
);
let got: Vec<u8> = pixs.iter().map(|&v| v as u8).collect();
assert_eq!(
got,
want.to_vec(),
"Han Xin pixs must match bwip-js byte-for-byte for input 'HELLO' L2 mask 2",
);
}
#[test]
fn encode_with_mask_matches_bwip_js_hello_l2_m1() {
#[rustfmt::skip]
let want: [u8; 529] = [
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,
1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1,
1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0,
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1,
1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
];
let (pixs, _) = encode_with_mask(b"HELLO", 2, 1).expect(
"encode_with_mask(b\"HELLO\", L2, mask=1) (Han Xin V1 L2 mask-1 byte-for-byte oracle; (col+row)%2 path) must succeed",
);
let got: Vec<u8> = pixs.iter().map(|&v| v as u8).collect();
assert_eq!(got, want.to_vec(), "Han Xin v1 L2 mask 1 byte-for-byte");
}
#[test]
fn encode_with_mask_matches_bwip_js_hello_l2_m3() {
#[rustfmt::skip]
let want: [u8; 529] = [
1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1,
1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1,
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1,
1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0,
0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0,
1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1,
1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1,
0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
];
let (pixs, _) = encode_with_mask(b"HELLO", 2, 3).expect(
"encode_with_mask(b\"HELLO\", L2, mask=3) (Han Xin V1 L2 mask-3 byte-for-byte oracle; arithmetic-heavy modulus-chain path) must succeed",
);
let got: Vec<u8> = pixs.iter().map(|&v| v as u8).collect();
assert_eq!(got, want.to_vec(), "Han Xin v1 L2 mask 3 byte-for-byte");
}
#[test]
fn mask_value_per_mask_boundaries() {
for r in 1..5 {
for c in 1..5 {
assert_eq!(mask_value(0, c, r), 1, "m=0 c={c} r={r}");
}
}
assert_eq!(mask_value(1, 1, 1), 0);
assert_eq!(mask_value(1, 2, 1), 1);
assert_eq!(mask_value(1, 1, 2), 1);
assert_eq!(mask_value(1, 2, 2), 0);
assert_eq!(mask_value(1, 3, 4), 1); assert_eq!(mask_value(2, 1, 1), 1);
assert_eq!(mask_value(2, 3, 3), 0);
assert_eq!(mask_value(2, 2, 1), 0);
assert_eq!(mask_value(2, 1, 2), 1);
assert_eq!(mask_value(3, 1, 1), 0);
assert_eq!(mask_value(3, 2, 2), 0);
assert_eq!(mask_value(3, 1, 2), 0);
assert_eq!(mask_value(3, 3, 2), 1);
}
#[test]
fn rs_encode_gf16_and_gf256_simple_outputs() {
let ecc = rs_encode_gf16(&[1, 2, 3], 4);
assert_eq!(ecc.len(), 4);
for n in &ecc {
assert!(*n <= 15, "nibble {n} out of range");
}
let ecc = rs_encode_gf256(&[0xAB, 0xCD, 0xEF], 8);
assert_eq!(ecc.len(), 8);
let ecc1 = rs_encode_gf256(&[1, 2, 3], 4);
let ecc2 = rs_encode_gf256(&[1, 2, 3], 4);
assert_eq!(ecc1, ecc2, "rs_encode must be deterministic");
assert_eq!(ecc1.len(), 4);
let ecc_a = rs_encode_gf256(&[1, 2, 3], 4);
let ecc_b = rs_encode_gf256(&[4, 5, 6], 4);
assert_ne!(ecc_a, ecc_b, "different inputs must give different ecc");
}
#[test]
fn parse_eclevel_default_and_per_arm_anchors() {
use crate::options::Options;
assert_eq!(parse_eclevel(&Options::default()).unwrap(), 1);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", "L1")).unwrap(),
1
);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", "L2")).unwrap(),
2
);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", "L3")).unwrap(),
3
);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", "L4")).unwrap(),
4
);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", "1")).unwrap(),
1
);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", "2")).unwrap(),
2
);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", "3")).unwrap(),
3
);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", "4")).unwrap(),
4
);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", "l2")).unwrap(),
2
);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", "l4")).unwrap(),
4
);
assert_eq!(
parse_eclevel(&Options::default().with("eclevel", " L3 ")).unwrap(),
3
);
for input in ["", "L5", "0", "5", "LL1", "lvl-3"] {
let err = parse_eclevel(&Options::default().with("eclevel", input)).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("eclevel={input:?} must yield InvalidData; got {err:?}");
};
let want_echo = format!("{input:?}");
assert!(
msg.contains("Han Xin Code:")
&& msg.contains("invalid eclevel")
&& msg.contains(&want_echo)
&& msg.contains("L1..L4"),
"eclevel={input:?} must pin symbology tag + 'invalid eclevel' + {want_echo:?} \
echo + L1..L4 hint; got {msg:?}"
);
}
}
#[test]
fn parse_mask_opt_arms_and_range_guard() {
use crate::options::Options;
assert_eq!(parse_mask_opt(&Options::default()).unwrap(), None);
assert_eq!(
parse_mask_opt(&Options::default().with("mask", "0")).unwrap(),
Some(0)
);
assert_eq!(
parse_mask_opt(&Options::default().with("mask", "1")).unwrap(),
Some(1)
);
assert_eq!(
parse_mask_opt(&Options::default().with("mask", "2")).unwrap(),
Some(2)
);
assert_eq!(
parse_mask_opt(&Options::default().with("mask", "3")).unwrap(),
Some(3)
);
assert_eq!(
parse_mask_opt(&Options::default().with("mask", " 2 ")).unwrap(),
Some(2)
);
for input in ["4", "9", "-1", "abc", ""] {
let err = parse_mask_opt(&Options::default().with("mask", input)).unwrap_err();
let crate::error::Error::InvalidData(msg) = err else {
panic!("mask={input:?} must yield InvalidData; got {err:?}");
};
let want_echo = format!("{input:?}");
assert!(
msg.contains("Han Xin Code:")
&& msg.contains("invalid mask")
&& msg.contains(&want_echo)
&& msg.contains("0..=3"),
"mask={input:?} must pin symbology tag + 'invalid mask' + {want_echo:?} \
echo + 0..=3 hint; got {msg:?}"
);
}
}
#[test]
fn trmv_horizontal_mirror_index_and_aplot_writes_two_cells() {
assert_eq!(trmv(0, 0, 5), 4);
assert_eq!(trmv(4, 0, 5), 0);
assert_eq!(trmv(0, 1, 5), 9);
assert_eq!(trmv(2, 2, 5), 12);
assert_eq!(trmv(4, 4, 5), 20);
assert_eq!(trmv(0, 0, 3), 2);
assert_eq!(trmv(2, 0, 3), 0);
assert_eq!(trmv(1, 1, 3), 4);
let mut pixs = vec![0i8; 25];
aplot(&mut pixs, 0, 1, 7, 5);
assert_eq!(pixs[9], 7, "primary cell trmv(x, y) at index 9");
assert_eq!(pixs[3], 7, "mirror cell trmv(y, x) at index 3");
let lit: Vec<_> = pixs.iter().enumerate().filter(|(_, &v)| v != 0).collect();
assert_eq!(
lit.len(),
2,
"exactly 2 cells set (kills `aplot with ()` and missing-write mutants)"
);
let mut pixs = vec![0i8; 25];
aplot(&mut pixs, 2, 2, 1, 5);
assert_eq!(pixs[12], 1, "diagonal: single cell");
let lit: Vec<_> = pixs.iter().enumerate().filter(|(_, &v)| v != 0).collect();
assert_eq!(lit.len(), 1, "diagonal aplot writes ONE cell");
let mut pixs = vec![0i8; 9];
aplot(&mut pixs, 0, 0, 42, 3);
assert_eq!(pixs[trmv(0, 0, 3)], 42);
}
#[test]
fn encode_rs_blocks_segregates_data_then_ecc_in_order() {
let out = encode_rs_blocks(&[], &[None, None, None]);
assert!(out.is_empty(), "all-None layout + empty data → empty");
let out = encode_rs_blocks(&[0u8; 4], &[Some((1, 4, 2)), None, None]);
assert_eq!(out.len(), 6, "1×(4 data + 2 ECC) → 6 bytes total");
assert_eq!(
out,
vec![0u8; 6],
"all-zero data → all-zero ECC → all-zero output"
);
let out = encode_rs_blocks(&[1, 2, 3, 4], &[Some((2, 2, 1)), None, None]);
assert_eq!(out.len(), 6, "2×(2 data + 1 ECC) → 6 bytes total");
assert_eq!(
&out[..4],
&[1, 2, 3, 4],
"data section must be the original data in order"
);
let out = encode_rs_blocks(&[10, 20, 30], &[Some((1, 2, 1)), Some((1, 1, 1)), None]);
assert_eq!(
out.len(),
5,
"1×(2 data + 1 ECC) + 1×(1 data + 1 ECC) → 5 bytes total"
);
assert_eq!(
&out[..3],
&[10, 20, 30],
"data must span both blocks in input order"
);
}
#[test]
fn alloc_pixs_returns_minus_one_filled_size_squared() {
assert!(alloc_pixs(0).is_empty(), "size=0 → empty Vec");
let p = alloc_pixs(1);
assert_eq!(p, vec![-1], "size=1 → [-1]");
assert_eq!(p.len(), 1);
let p = alloc_pixs(2);
assert_eq!(p.len(), 4, "size=2 → 4 cells (2*2)");
assert!(p.iter().all(|&c| c == -1), "all -1");
let p = alloc_pixs(3);
assert_eq!(p.len(), 9, "size=3 → 9 cells (3*3, NOT 3+3=6)");
assert!(p.iter().all(|&c| c == -1));
let p = alloc_pixs(10);
assert_eq!(p.len(), 100, "size=10 → 100 cells");
assert!(p.iter().all(|&c| c == -1));
for size in [23, 25, 27] {
let p = alloc_pixs(size);
assert_eq!(p.len(), size * size, "size={size} → {} cells", size * size);
assert!(
p.iter().all(|&c| c == -1),
"size={size}: every cell must be -1"
);
}
let p = alloc_pixs(5);
assert_ne!(p[0], 0, "sentinel is -1, NOT 0 (data-placement would skip)");
assert_ne!(
p[0], 1,
"sentinel is -1, NOT 1 (would look like a dark cell)"
);
assert_eq!(p[0], -1, "sentinel is exactly -1");
}
#[test]
fn hanxin_metrics_alig_step_sentinel_pinned() {
assert_eq!(HANXIN_METRICS[0].alig_step, -1, "v1 alig_step sentinel");
assert_eq!(
HANXIN_METRICS[1].alig_step, -1,
"v2 alig_step sentinel (L82)"
);
assert_eq!(
HANXIN_METRICS[2].alig_step, -1,
"v3 alig_step sentinel (L83)"
);
assert_eq!(HANXIN_METRICS[0].alig_count, 0);
assert_eq!(HANXIN_METRICS[1].alig_count, 0);
assert_eq!(HANXIN_METRICS[2].alig_count, 0);
assert_eq!(HANXIN_METRICS[3].alig_step, 14);
assert_eq!(HANXIN_METRICS[3].alig_count, 1);
}
#[test]
fn place_data_pads_with_zero_past_codeword_stream() {
let size = 7usize;
let cws: [u8; 2] = [0b1010_1010, 0b0000_0000];
let mut pixs = alloc_pixs(size);
place_data(&mut pixs, &cws, size);
let expect_cw0 = [1, 0, 1, 0, 1, 0, 1, 0];
for (k, e) in expect_cw0.iter().enumerate() {
assert_eq!(pixs[k], *e as i8, "data cell {k} from cws[0]");
}
for k in 16..size * size {
assert_eq!(pixs[k], 0, "padding cell {k} must be 0 (cw_idx >= len)");
}
}
#[test]
fn build_mask_grid_uses_correct_version_index() {
let size = 31usize; assert_eq!((size - 23) / 2, 4);
assert_ne!((size / 23) / 2, 4, "mutant `-`→`/` would mis-index");
assert_ne!((size - 23) % 2, 4, "mutant `/`→`%` would mis-index");
let mut scratch = alloc_pixs(size);
place_alignment_patterns(&mut scratch, &HANXIN_METRICS[4], size);
place_corner_finders(&mut scratch, size);
zero_function_info_cells(&mut scratch, size);
let m = 1u8;
let mut expected = vec![0u8; size * size];
for j in 0..size {
for i in 0..size {
let idx = i + j * size;
let is_data = scratch[idx] == -1;
let mv = mask_value(m, i + 1, j + 1);
expected[idx] = if mv == 0 && is_data { 1 } else { 0 };
}
}
let pixs = alloc_pixs(size);
let got = build_mask_grid_data_zone_only(&pixs, m, size);
assert_eq!(got, expected, "mask grid must use v5 (index 4) geometry");
let mut v1 = alloc_pixs(size);
place_alignment_patterns(&mut v1, &HANXIN_METRICS[0], size); place_corner_finders(&mut v1, size);
zero_function_info_cells(&mut v1, size);
let func_v5 = scratch.iter().filter(|&&v| v != -1).count();
let func_v1 = v1.iter().filter(|&&v| v != -1).count();
assert!(
func_v5 > func_v1,
"v5 must place extra alignment function cells"
);
}
#[test]
fn pick_best_mask_breaks_ties_toward_lowest_index() {
let size = 23usize;
const DATABITS: &str = "0000001111000001000111010111011101111011010110000000011111111110101110010100010010001011101010111011010011011101011011110110110100010010000000011101010100000100100010011100100111100110110111110101000100001";
let mut scratch = alloc_pixs(size);
place_alignment_patterns(&mut scratch, &HANXIN_METRICS[0], size);
place_corner_finders(&mut scratch, size);
zero_function_info_cells(&mut scratch, size);
let mut bits = DATABITS.chars();
let mut pixs = scratch.clone();
for c in pixs.iter_mut() {
if *c == -1 {
*c = if bits.next() == Some('1') { 1 } else { 0 };
}
}
assert!(
bits.next().is_none(),
"DATABITS length must match data-cell count"
);
let mut scores = [0u32; 4];
for mm in 0u8..=3 {
let g = build_mask_grid_data_zone_only(&pixs, mm, size);
let masked = compute_masked_symbol(&pixs, &g);
scores[mm as usize] = evalfull(&masked, size);
}
let min = *scores.iter().min().unwrap();
assert_eq!(scores[2], min, "mask 2 is a minimum");
assert_eq!(scores[3], min, "mask 3 ties at the minimum");
assert!(
scores[0] > min && scores[1] > min,
"masks 0,1 are strictly worse"
);
assert_eq!(
pick_best_mask(&pixs, size),
2,
"ties must resolve to the lowest mask index (mutant `<=` returns 3)"
);
}
#[test]
fn encode_final_codewords_rbit_predicate_is_invariant() {
for m in HANXIN_METRICS.iter() {
let cb = m.cap_bits;
assert_ne!(
cb % 8,
0,
"v{}: cap_bits {cb} unexpectedly divisible by 8",
m.version
);
let orig = (cb % 8) > 0;
let mut_div = (cb / 8) > 0;
let mut_add = (cb + 8) > 0;
assert!(orig, "v{}: original rbit>0", m.version);
assert_eq!(orig, mut_div, "v{}: `%`→`/` changes rbit>0", m.version);
assert_eq!(orig, mut_add, "v{}: `%`→`+` changes rbit>0", m.version);
}
let cws = [1u8, 2, 3];
assert_eq!(
interleave_codewords(&cws, 5).len(),
4,
"rbit>0 → +1 trailing"
);
assert_eq!(
interleave_codewords(&cws, 1).len(),
4,
"any positive rbit → +1"
);
assert_eq!(
interleave_codewords(&cws, 0).len(),
3,
"rbit==0 → no trailing"
);
}
#[test]
#[allow(clippy::assign_op_pattern, clippy::nonminimal_bool)]
fn hanxin_alignment_equivalence_notes() {
for alnn in 0u32..=10 {
for stag in 0u32..=1 {
assert_eq!(
(alnn + stag) % 2,
(alnn.wrapping_sub(stag)) % 2,
"L486 identity fails at alnn={alnn} stag={stag}"
);
}
}
let (mut a, mut b) = (0u32, 0u32);
for _ in 0..32 {
a = 1 - a; b = 1 + b; assert_eq!(a % 2, b % 2, "L500 parity stream diverged");
}
for alnk in [14usize, 16, 17, 18, 19, 20, 21] {
let j = alnk;
assert_eq!(j % alnk, 0, "j==alnk ⇒ j%alnk==0 ⇒ cond forced true");
assert!(!(j < alnk));
assert!(j <= alnk);
}
for (idx, m) in HANXIN_METRICS.iter().enumerate() {
if m.alig_count == 0 {
continue;
}
let size = m.size as usize;
let mut real = alloc_pixs(size);
place_alignment_patterns(&mut real, m, size);
for mu in 0u8..8 {
let got = alig_with_mutant(m, size, mu);
assert_eq!(
got, real,
"alignment mutant {mu} changed output for HANXIN_METRICS[{idx}] (v{}) — NOT equivalent",
m.version
);
}
}
}
fn alig_with_mutant(metric: &HanXinMetric, size: usize, mu: u8) -> Vec<i8> {
let mut pixs = alloc_pixs(size);
let alnk = metric.alig_step as usize;
let alnn = metric.alig_count as usize;
let alnr = size - alnk * alnn;
let mut i = 0usize;
let mut stag = 0usize;
loop {
if i >= size {
break;
}
for j in 0..size {
let l483 = if mu == 0 {
j + alnr <= size
} else {
j + alnr < size
};
let cond = if l483 {
let lt = if mu == 1 { j <= alnk } else { j < alnk };
((j / alnk + stag) % 2 == 0 && !(i == 0 && lt)) || (j % alnk == 0)
} else if mu == 2 {
(alnn.wrapping_sub(stag)) % 2 == 0
} else {
(alnn + stag) % 2 == 0
};
if cond {
aplot(&mut pixs, j, i, 1, size);
if i + 1 < size && j + 1 < size {
aplot(&mut pixs, j + 1, i + 1, 0, size);
}
}
}
if i + alnr == size {
i = i + alnr - 1;
} else {
i += alnk;
}
stag = if mu == 3 { 1 + stag } else { 1 - stag };
}
let mut i = alnk;
while i <= size.saturating_sub(2) {
if (i / alnk) % 2 != 0 {
pixs[trmv(0, i - 1, size)] = 0;
pixs[trmv(0, i + 1, size)] = 0;
pixs[trmv(1, i - 1, size)] = 0;
pixs[trmv(1, i, size)] = 0;
let c515 = match mu {
4 => i - 1,
5 => i,
_ => i + 1,
};
pixs[trmv(1, c515, size)] = 0;
pixs[trmv(i - 1, 0, size)] = 0;
pixs[trmv(i + 1, 0, size)] = 0;
pixs[trmv(i - 1, 1, size)] = 0;
pixs[trmv(i, 1, size)] = 0;
let c520 = match mu {
6 => i - 1,
7 => i,
_ => i + 1,
};
pixs[trmv(c520, 1, size)] = 0;
}
if pixs[trmv(size - 1, i - 1, size)] != 1 {
pixs[trmv(size - 1, i - 1, size)] = 0;
pixs[trmv(size - 2, i - 1, size)] = 0;
pixs[trmv(size - 2, i, size)] = 0;
pixs[trmv(size - 2, i + 1, size)] = 0;
pixs[trmv(size - 1, i + 1, size)] = 0;
pixs[trmv(i - 1, size - 1, size)] = 0;
pixs[trmv(i - 1, size - 2, size)] = 0;
pixs[trmv(i, size - 2, size)] = 0;
pixs[trmv(i + 1, size - 2, size)] = 0;
pixs[trmv(i + 1, size - 1, size)] = 0;
}
i += alnk;
}
pixs
}
}