anyd 0.1.1

From-scratch encoding and decoding of 1D and 2D barcodes with lossless round-trip and live-video detection
Documentation
//! Rectangular Micro QR (rMQR, ISO/IEC 23941) tests.
//!
//! Two kinds of validation:
//!  1. **External reference vectors** — full module bitmaps generated by the
//!     independent `rmqrcode` library, including single- and multi-block sizes, both
//!     EC levels, and interior-alignment sizes. Our encoder must reproduce them
//!     bit-for-bit.
//!  2. **Round-trip identity** across all 32 sizes and both EC levels.

use anyd::codes::rmqr::{RmqrDecoder, RmqrEcLevel, RmqrEncoder, RmqrMeta, RmqrSize};
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(width: usize, height: usize, ec: RmqrEcLevel) -> RmqrMeta {
    RmqrMeta {
        size: RmqrSize::from_dimensions(width, height).unwrap(),
        ec_level: ec,
    }
}

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

/// Encode a byte segment against a reference bitmap, then assert round-trip identity.
fn check(content: &[u8], w: usize, h: usize, ec: RmqrEcLevel, flat: &str) {
    let m = meta(w, h, ec);
    let sym = Symbol::new(
        Symbology::RectMicroQrCode,
        vec![Segment::byte(content.to_vec())],
        SymbolMeta::Rmqr(m),
    );
    let enc = RmqrEncoder::new().encode(&sym).unwrap();
    assert_matrix(&enc, w, h, flat);

    let decoded = RmqrDecoder::new().decode(&enc).unwrap();
    assert_eq!(
        decoded.segments, sym.segments,
        "segments differ after decode"
    );
    assert_eq!(decoded.meta, sym.meta, "meta differs after decode");
    assert_eq!(
        RmqrEncoder::new().encode(&decoded).unwrap(),
        enc,
        "re-encode differs"
    );
}

#[test]
fn r7x43_m() {
    check(
        b"HELLO",
        43,
        7,
        RmqrEcLevel::M,
        "1111111010101010101011101010101010101010111100000100101101111001010001110101001100010110111010101101111110111100011000010111111111011101001101010011110000000110111000010001101110100010111011001111010001101111001010110000010111010101000101010011010110110100011111111010101010101011101010101010101011111",
    );
}

#[test]
fn r7x43_h() {
    check(
        b"Hi",
        43,
        7,
        RmqrEcLevel::H,
        "1111111010101010101011101010101010101010111100000101101000010001011111000001000100000110111010110100011000111000110110010100111111011101010111001010100101100000011011010001101110100110100001101110101001101101101010110000010011010101110101111000111101010100011111111010101010101011101010101010101011111",
    );
}

#[test]
fn r11x27_m() {
    check(
        b"test",
        27,
        11,
        RmqrEcLevel::M,
        "111111101010101010101010111100000101110110001000011001101110100111010001001101001101110100010001000111111010101110100001011110011110001100000100110100100001110110111111101100100111100111111000000001100101011001010001111110000001101000010110101101110000110110100101010001111010101010101010101011111",
    );
}

#[test]
fn r13x27_h() {
    check(
        b"AB",
        27,
        13,
        RmqrEcLevel::H,
        "111111101010101010101010111100000101001110000001100101101110101010101001001010001101110101011111010000110100101110101011000110011000101100000101011000100111010110111111100111111001100111111000000001000000100100001000111110000101110100011111111010111000101110110011010001100010100100011011000010101100000011111110010110010001111010101010101010101011111",
    );
}

// Asymmetric multi-block sizes: the reference `rmqrcode` library has a codeword-
// interleaving bug (it drops the longest block's tail data codeword), so its bitmaps
// for these sizes are non-conformant and cannot be used as reference vectors. We
// instead exercise our ISO-standard interleaving (identical to the validated QR
// module) via round-trip identity, which spans multiple blocks with these payloads.
#[test]
fn r9x77_h_two_blocks_roundtrip() {
    roundtrip_size(
        RmqrSize::from_dimensions(77, 9).unwrap(),
        RmqrEcLevel::H,
        Segment::byte(b"block12 payload".to_vec()),
    );
}

#[test]
fn r11x99_m_two_blocks_roundtrip() {
    roundtrip_size(
        RmqrSize::from_dimensions(99, 11).unwrap(),
        RmqrEcLevel::M,
        Segment::byte(b"R11x99 two data blocks payload here".to_vec()),
    );
}

#[test]
fn r13x139_m_three_blocks_roundtrip() {
    roundtrip_size(
        RmqrSize::from_dimensions(139, 13).unwrap(),
        RmqrEcLevel::M,
        Segment::byte(b"R13x139 three block interleave test payload content".to_vec()),
    );
}

#[test]
fn r17x139_m_four_blocks() {
    let content = vec![b'X'; 140];
    check(
        &content,
        139,
        17,
        RmqrEcLevel::M,
        "11111110101010101010101010111010101010101010101010101011101010101010101010101010101110101010101010101010101010111010101010101010101010101111000001001001010010000000110100110111000111010100110001010001000101111100110010100101111001101011110111010000010111000101111100110011111001101110101110100001011000111110101111111010111100000011111001000011101110001101010111101011000000000110110111011111001101010101100000001011110111010010011110011100011101010110000110100011000100000110101011010111101110100000000010001011111001100101000100110011010111101111111011001011101000010010100100101001111110110011101011111010011100011100001010100111000100010010010010001001001111111111000111000100011100001010101100000100001111011001000111011010010101011000110101011001000100001111110011001010010101100110101111011101000001001100010111110011001111110011111110010011000001101000111001111100001010111110000001000110001110010000110101010100101100000000011011011101010100110101010110000000101110000000011000011101111100010101111011110010001001010100011011001101000000011010000100001000101111100110010100000011001101011110111111100100110100010111001010010001001100100110001100101101101000110001110010001010111100010001001001001000100100111111110100011100010001110000101000101101000011000100011100101101111001111111011011110100000100010001000110111100101000010110011010111101110100000100110001011111001100111110101011101000011110100011100011101101110100010000111000000100011010111011110011010101110010110000000001101101110111010011010101011000000010001000100001000001011101110110000100011100010110100101010001101101010011111011101000000000100010111110011001010001001100110101111011111101111011011001111100111001101010011110111111010010010100100011001111001011101001110001000100100100100010010011111111110001110001000111000101111110110111011100011001101101010110100100011101100110100001010111011101111100110010100101011001101011110111010000010011000101111100110000110001111110001001101110001100001111111101010010000101110100111101100111101110001101010111101011000000000110110111011110001101010101100011011010110111100011001001111111111101011101011000111010000111010111010011010111101110100001010010001011111001100101000101110011010111101110001100011110101010101010101010101011101010101010101010101010101110101010101010101010101010111010101010101010101010101011101010101010101010101011111",
    );
}

// --- Round-trip across every size and level via explicit metadata ---

fn roundtrip_size(size: RmqrSize, ec: RmqrEcLevel, seg: Segment) {
    let sym = Symbol::new(
        Symbology::RectMicroQrCode,
        vec![seg],
        SymbolMeta::Rmqr(RmqrMeta { size, ec_level: ec }),
    );
    let enc = RmqrEncoder::new().encode(&sym).unwrap();
    let decoded = RmqrDecoder::new().decode(&enc).unwrap();
    assert_eq!(decoded.segments, sym.segments, "{} segments", size.name());
    assert_eq!(decoded.meta, sym.meta, "{} meta", size.name());
    assert_eq!(
        RmqrEncoder::new().encode(&decoded).unwrap(),
        enc,
        "{} re-encode",
        size.name()
    );
}

#[test]
fn every_size_and_level_roundtrips() {
    for size in RmqrSize::all() {
        for ec in [RmqrEcLevel::M, RmqrEcLevel::H] {
            // A tiny numeric payload fits even the smallest capacity (R7x43-H, 24 bits).
            roundtrip_size(size, ec, Segment::numeric(b"42".to_vec()));
        }
    }
}

#[test]
fn builder_selects_and_roundtrips() {
    let enc = RmqrEncoder::new();
    for (content, level) in [
        ("anydcode rMQR", RmqrEcLevel::M),
        ("HELLO WORLD 12345", RmqrEcLevel::H),
        ("https://example.com/rmqr?q=1", RmqrEcLevel::M),
    ] {
        let sym = enc.build_text(content, level).unwrap();
        let e = enc.encode(&sym).unwrap();
        let d = RmqrDecoder::new().decode(&e).unwrap();
        assert_eq!(d.text().as_deref(), Some(content));
        assert_eq!(enc.encode(&d).unwrap(), e);
    }
}

#[test]
fn mixed_segments_preserve_boundaries() {
    let size = RmqrSize::from_dimensions(99, 11).unwrap();
    let segs = vec![
        Segment::numeric(b"12345".to_vec()),
        Segment::alphanumeric(b"ABC".to_vec()),
        Segment::byte(b"xyz!".to_vec()),
    ];
    let sym = Symbol::new(
        Symbology::RectMicroQrCode,
        segs.clone(),
        SymbolMeta::Rmqr(RmqrMeta {
            size,
            ec_level: RmqrEcLevel::M,
        }),
    );
    let e = RmqrEncoder::new().encode(&sym).unwrap();
    let d = RmqrDecoder::new().decode(&e).unwrap();
    assert_eq!(d.segments, segs);
}

#[test]
fn survives_correctable_error() {
    let enc = RmqrEncoder::new();
    let sym = enc.build_text("RESILIENT rMQR", RmqrEcLevel::H).unwrap();
    let Encoding::Matrix(mut m) = enc.encode(&sym).unwrap() else {
        panic!("expected matrix");
    };
    // Flip a couple of interior data modules.
    for (x, y) in [(15usize, 3usize), (20, 4)] {
        if x < m.width() && y < m.height() {
            let v = m.get(x, y);
            m.set(x, y, !v);
        }
    }
    let d = RmqrDecoder::new().decode(&Encoding::Matrix(m)).unwrap();
    assert_eq!(d.text().as_deref(), Some("RESILIENT rMQR"));
}

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