anyd 0.1.1

From-scratch encoding and decoding of 1D and 2D barcodes with lossless round-trip and live-video detection
Documentation
//! Micro QR Code tests.
//!
//! Two kinds of validation:
//!  1. **External reference vectors** — full module bitmaps generated by the
//!     independent `segno` library (ISO/IEC 18004), covering every version M1–M4,
//!     every EC level, all four masks and all four modes. Our encoder must
//!     reproduce them bit-for-bit.
//!  2. **Round-trip identity** — encode → decode → re-encode is byte-identical and
//!     recovers the exact segments and metadata.

use anyd::codes::microqr::{
    MicroEcLevel, MicroMask, MicroQrDecoder, MicroQrEncoder, MicroQrMeta, MicroVersion,
};
use anyd::output::{BitMatrix, Encoding};
use anyd::segment::Segment;
use anyd::symbol::{Symbol, SymbolMeta};
use anyd::symbology::Symbology;
use anyd::traits::{Decode, Encode};

fn meta(v: MicroVersion, ec: MicroEcLevel, mask: u8) -> MicroQrMeta {
    MicroQrMeta {
        version: v,
        ec_level: ec,
        mask: MicroMask::new(mask).unwrap(),
    }
}

fn symbol(seg: Segment, m: MicroQrMeta) -> Symbol {
    Symbol::new(Symbology::MicroQrCode, vec![seg], SymbolMeta::MicroQr(m))
}

/// Compare an encoded matrix against a flattened `0`/`1` reference of side `size`.
fn assert_matrix(enc: &Encoding, size: usize, flat: &str) {
    let Encoding::Matrix(m) = enc else {
        panic!("expected a matrix encoding");
    };
    assert_eq!(m.width(), size);
    assert_eq!(m.height(), size);
    let bytes = flat.as_bytes();
    assert_eq!(bytes.len(), size * size, "reference length mismatch");
    for y in 0..size {
        for x in 0..size {
            let want = bytes[y * size + x] == b'1';
            assert_eq!(m.get(x, y), want, "module ({x},{y}) differs from reference");
        }
    }
}

/// Encode against a reference bitmap, then assert full round-trip identity.
fn check(seg: Segment, m: MicroQrMeta, size: usize, flat: &str) {
    let sym = symbol(seg, m.clone());
    let enc = MicroQrEncoder::new().encode(&sym).unwrap();
    assert_matrix(&enc, size, flat);

    let decoded = MicroQrDecoder::new().decode(&enc).unwrap();
    assert_eq!(
        decoded.segments, sym.segments,
        "segments differ after decode"
    );
    assert_eq!(decoded.meta, sym.meta, "meta differs after decode");
    let re = MicroQrEncoder::new().encode(&decoded).unwrap();
    assert_eq!(re, enc, "re-encoding is not byte-identical");
}

#[test]
fn m1_numeric() {
    check(
        Segment::numeric(b"12345".to_vec()),
        meta(MicroVersion::M1, MicroEcLevel::Detection, 2),
        11,
        "1111111010110000010110101110101001011101000010111010111100000100111111111010000000000011110011100110101000110011110000011",
    );
}

#[test]
fn m2_numeric_l() {
    check(
        Segment::numeric(b"0123456789".to_vec()),
        meta(MicroVersion::M2, MicroEcLevel::L, 1),
        13,
        "1111111010101100000101110110111010001011011101000111101110101000010000010111011111111000111000000000110011010000101010100011010001111100001111001010000111001101010010111",
    );
}

#[test]
fn m2_alphanumeric_m() {
    check(
        Segment::alphanumeric(b"ABCDE".to_vec()),
        meta(MicroVersion::M2, MicroEcLevel::M, 0),
        13,
        "1111111010101100000101100110111010111101011101001100101110100101010000010101011111111000010000000000101011100111101100000110011111110110101001101000101111101001000111100",
    );
    check(
        Segment::alphanumeric(b"HELLO".to_vec()),
        meta(MicroVersion::M2, MicroEcLevel::M, 3),
        13,
        "1111111010101100000100100010111010111111011101001101101110101001010000010010011111111001111000000001100011101000100110101110111111101010100001101010101011101110010001110",
    );
}

#[test]
fn m3_byte_l() {
    check(
        Segment::byte(b"Hello!".to_vec()),
        meta(MicroVersion::M3, MicroEcLevel::L, 3),
        15,
        "111111101010101100000101111011101110100010101101110100101100101110100001001100000100100001111111101011100000000000111110111110010010000000010110100111100010001000111010010100100101100011100010100001010110110010111110011011101",
    );
}

#[test]
fn m3_numeric_m() {
    check(
        Segment::numeric(b"987654".to_vec()),
        meta(MicroVersion::M3, MicroEcLevel::M, 1),
        15,
        "111111101010101100000101010111101110100101011101110100111010101110101010111100000100010101111111101101011000000001101010100000111000100001011001000110100100110111011010011100111010101111011000100010100001000111110110110111000",
    );
    check(
        Segment::alphanumeric(b"AB12".to_vec()),
        meta(MicroVersion::M3, MicroEcLevel::M, 2),
        15,
        "111111101010101100000100000100101110100100101101110100110100101110100111101100000101101100111111101001110000000000000110100011001110100000001001010100100010100101110010101101011100110001111111111011001101000101110000011110100",
    );
}

#[test]
fn m4_byte_l() {
    check(
        Segment::byte(b"Test 123".to_vec()),
        meta(MicroVersion::M4, MicroEcLevel::L, 0),
        17,
        "1111111010101010110000010100001011101110100010000111011101010001101110111010001001101100000101110000101111111011011100100000000011000001100101110111111010110100010000000010000001001110001011100011000000101011000000111010101110101111110000101110101101100100110000011111000011010111111111001",
    );
}

#[test]
fn m4_byte_m() {
    check(
        Segment::byte(b"hello world".to_vec()),
        meta(MicroVersion::M4, MicroEcLevel::M, 2),
        17,
        "1111111010101010110000010000010100101110101111111001011101010100100010111010010010100100000100011110111111111011100100100000000100000001101011110111101010111011010100010111101011110110011000001001110000001000011101001110100111011101100111111111000101001100001011001100110111111011111011000",
    );
}

#[test]
fn m4_alphanumeric_q() {
    check(
        Segment::alphanumeric(b"ABC123".to_vec()),
        meta(MicroVersion::M4, MicroEcLevel::Q, 0),
        17,
        "1111111010101010110000010110110000101110101110010011011101000010111110111010001111001100000100010001001111111011100111000000000101110001101101001111111100010101010100001010101110110001110011001111010100111100101100100111101101100101101101101001001011110010111111001010110110010100101111111",
    );
    check(
        Segment::numeric(b"999".to_vec()),
        meta(MicroVersion::M4, MicroEcLevel::Q, 1),
        17,
        "1111111010101010110000010011100000101110100110101111011101010111111110111010000011100100000101110011001111111000001101100000000101110010101100011100101110011001101011000010111110010100100010101100100011001110111101101111101111001011011110111111010110110110011000000011001111011010101100100",
    );
}

// --- Round-trip coverage via the builder (auto version + mask selection) ---

fn roundtrip(segments: Vec<Segment>, level: MicroEcLevel) {
    let enc = MicroQrEncoder::new();
    let sym = enc.build(segments, level).unwrap();
    let encoding = enc.encode(&sym).unwrap();
    let decoded = MicroQrDecoder::new().decode(&encoding).unwrap();
    assert_eq!(decoded.segments, sym.segments);
    assert_eq!(decoded.meta, sym.meta);
    assert_eq!(enc.encode(&decoded).unwrap(), encoding);
}

#[test]
fn builder_roundtrips() {
    roundtrip(
        vec![Segment::numeric(b"12345".to_vec())],
        MicroEcLevel::Detection,
    );
    roundtrip(vec![Segment::numeric(b"98765".to_vec())], MicroEcLevel::L);
    roundtrip(
        vec![Segment::alphanumeric(b"HELLO WORLD".to_vec())],
        MicroEcLevel::M,
    );
    roundtrip(vec![Segment::byte(b"anydcode".to_vec())], MicroEcLevel::M);
    roundtrip(
        vec![Segment::byte("héllo".as_bytes().to_vec())],
        MicroEcLevel::L,
    );
    roundtrip(vec![Segment::numeric(b"5".to_vec())], MicroEcLevel::L);
    roundtrip(vec![Segment::numeric(b"42".to_vec())], MicroEcLevel::M);
    roundtrip(
        vec![
            Segment::alphanumeric(b"AB".to_vec()),
            Segment::numeric(b"12345".to_vec()),
        ],
        MicroEcLevel::M,
    );
}

#[test]
fn survives_correctable_error_m4() {
    // M4-Q corrects up to ~25%. Flip a single data module and still recover.
    let enc = MicroQrEncoder::new();
    let sym = enc
        .build(
            vec![Segment::alphanumeric(b"OK123".to_vec())],
            MicroEcLevel::Q,
        )
        .unwrap();
    let Encoding::Matrix(mut m) = enc.encode(&sym).unwrap() else {
        panic!("expected matrix");
    };
    // Flip an interior data module.
    let v = m.get(10, 10);
    m.set(10, 10, !v);
    let decoded = MicroQrDecoder::new().decode(&Encoding::Matrix(m)).unwrap();
    assert_eq!(decoded.segments, sym.segments);
}

#[test]
fn rejects_non_microqr_symbol() {
    let sym = Symbol::new(
        Symbology::QrCode,
        vec![Segment::numeric(b"1".to_vec())],
        SymbolMeta::Generic,
    );
    assert!(MicroQrEncoder::new().encode(&sym).is_err());
}

#[test]
fn wrong_size_matrix_is_undecodable() {
    let m = BitMatrix::new(12, 12, 2);
    assert!(MicroQrDecoder::new().decode(&Encoding::Matrix(m)).is_err());
}