qred 0.1.1

A fast, light-footprint native QR code generator with custom color styling.
Documentation
use crate::{mode_encoder::Mode, reed::BlockSpec};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcLevel {
    L = 0,  // 7% error correction
    M = 1,  // 15% error correction
    Q = 2,  // 25% error correction
    H = 3,  // 30% error correction
}

impl EcLevel {
    pub fn format_bits(&self) -> u32 {
        match self {
            EcLevel::L => 0b01,
            EcLevel::M => 0b00,
            EcLevel::Q => 0b11,
            EcLevel::H => 0b10,
        }
    }
}

// Full QRCode Spec
#[derive(Debug, Clone, Copy)]
pub struct QrSpec {
    pub version: usize,
    pub ec_level: EcLevel,
    pub total_codewords: usize, // Total bytes
    pub data_codewords: usize,  // Data bytes
    pub ec_codewords: usize,    // Error correction (ec) bytes
    pub num_blocks: usize,      // Number of RS blocks
}

impl QrSpec {
    pub fn matrix_dim(&self) -> usize {
        17 + 4 * self.version
    }

    pub fn char_count_bits(&self, mode: Mode) -> usize {
        match (mode, self.version) {
            (Mode::Numeric, 1..=9)          => 10,
            (Mode::Alphanumeric, 1..=9)     => 9,
            (Mode::Byte, 1..=9)             => 8,

            (Mode::Numeric, 10..=26)        => 12,
            (Mode::Alphanumeric, 10..=26)   => 11,
            (Mode::Byte, 10..=26)           => 16,
            
            (Mode::Numeric, 27..=40)        => 14,
            (Mode::Alphanumeric, 27..=40)   => 13,
            (Mode::Byte, 27..=40)           => 16,
            
            _ => unreachable!(),
        }
    }

    pub fn to_block_spec(&self) -> BlockSpec {
        let ec_codewords_per_block = self.ec_codewords / self.num_blocks;

        let base_data = self.data_codewords / self.num_blocks;
        let remainder = self.data_codewords % self.num_blocks;

        if remainder == 0 {
            BlockSpec {
                total_data_codewords: self.data_codewords,
                ec_codewords_per_block,
                g1_blocks: self.num_blocks,
                g1_data_per_block: base_data,
                g2_blocks: 0,
                g2_data_per_block: 0,
            }
        } else {
            let g2_blocks = remainder;
            let g1_blocks = self.num_blocks - g2_blocks;

            BlockSpec {
                total_data_codewords: self.data_codewords,
                ec_codewords_per_block,
                g1_blocks,
                g1_data_per_block: base_data,
                g2_blocks,
                g2_data_per_block: base_data + 1,
            }
        }
    }
}


// Centralized function to get specs of (version, error_correction_level)
pub fn get_qr_spec(version: usize, ec_level: EcLevel) -> QrSpec {
    let (total_codewords, data_codewords, num_blocks) = match (version, ec_level) {
        // Version 1
        (1, EcLevel::L) => (26, 19, 1),
        (1, EcLevel::M) => (26, 16, 1),
        (1, EcLevel::Q) => (26, 13, 1),
        (1, EcLevel::H) => (26, 9, 1),

        // Version 2
        (2, EcLevel::L) => (44, 34, 1),
        (2, EcLevel::M) => (44, 28, 1),
        (2, EcLevel::Q) => (44, 22, 1),
        (2, EcLevel::H) => (44, 16, 1),

        // Version 3
        (3, EcLevel::L) => (70, 55, 1),
        (3, EcLevel::M) => (70, 44, 1),
        (3, EcLevel::Q) => (70, 34, 2),
        (3, EcLevel::H) => (70, 26, 2),

        // Version 4
        (4, EcLevel::L) => (100, 80, 1),
        (4, EcLevel::M) => (100, 64, 2),
        (4, EcLevel::Q) => (100, 48, 2),
        (4, EcLevel::H) => (100, 36, 4),

        // Version 5
        (5, EcLevel::L) => (134, 108, 1),
        (5, EcLevel::M) => (134, 86, 2),
        (5, EcLevel::Q) => (134, 62, 4),
        (5, EcLevel::H) => (134, 46, 4),

        // Version 6
        (6, EcLevel::L) => (172, 136, 2),
        (6, EcLevel::M) => (172, 108, 4),
        (6, EcLevel::Q) => (172, 76, 4),
        (6, EcLevel::H) => (172, 60, 4),

        // Version 7
        (7, EcLevel::L) => (196, 156, 2),
        (7, EcLevel::M) => (196, 124, 4),
        (7, EcLevel::Q) => (196, 88, 6),
        (7, EcLevel::H) => (196, 66, 5),

        // Version 8
        (8, EcLevel::L) => (230, 184, 2),
        (8, EcLevel::M) => (230, 144, 6),
        (8, EcLevel::Q) => (230, 110, 6),
        (8, EcLevel::H) => (230, 86, 6),

        // Version 9
        (9, EcLevel::L) => (271, 216, 2),
        (9, EcLevel::M) => (271, 168, 8),
        (9, EcLevel::Q) => (271, 132, 8),
        (9, EcLevel::H) => (271, 100, 8),

        // Version 10
        (10, EcLevel::L) => (321, 256, 4),
        (10, EcLevel::M) => (321, 200, 8),
        (10, EcLevel::Q) => (321, 154, 8),
        (10, EcLevel::H) => (321, 122, 10),

        _ => todo!("Add entries for higher versions when needed"),
    };

    let ec_codewords = total_codewords - data_codewords;

    QrSpec {
        version,
        ec_level,
        total_codewords,
        data_codewords,
        ec_codewords,
        num_blocks,
    }
}

// UTIL: Alignment pattern coordinates for supported versions
pub fn get_alignment_coords(version: usize) -> Vec<usize> {
    match version {
        2 => vec![6, 18],
        3 => vec![6, 22],
        4 => vec![6, 26],
        5 => vec![6, 30],
        6 => vec![6, 34],
        7 => vec![6, 22, 38],
        8 => vec![6, 24, 42],
        9 => vec![6, 26, 46],
        10 => vec![6, 28, 50],
        _ => vec![],
    }
}

// UTIL: Get 2-bit value of Error Correction Level
pub fn ec_level_format_bits(ec_level: &str) -> u16 {
    match ec_level {
        "L" => 0b01,
        "M" => 0b00,
        "Q" => 0b11,
        "H" => 0b10,
        _   => 0b00,
    }
}

// UTIL: Mask formulae
pub fn is_mask_bit_set(mask: u8, r: usize, c: usize) -> bool {
    let r = r as isize;
    let c = c as isize;

    match mask {
        0 => (r+c) % 2 == 0,
        1 => r % 2 == 0,
        2 => c % 3 == 0,
        3 => (r+c) % 3 == 0,
        4 => ((r/2) + (c/3)) % 2 == 0,
        5 => ((r*c) % 2) + ((r*c) % 3) == 0,
        6 => (((r*c) % 2) + ((r*c) % 3)) % 2 == 0,
        7 => (((r+c) % 2) + ((r*c) % 3)) % 2 == 0,
        _ => false,
    }
}