use serde::Serialize; use std::sync::OnceLock; /// QR 码纠错级别 /// /// 决定 QR 码被遮挡/损毁后仍可扫描的能力。纠错越强,可存储的数据越少。 /// /// ```rust /// use qr_core::version::{EcLevel, Version}; /// /// let ver = Version::new(1).unwrap(); /// let h = ver.ec_info(EcLevel::H); /// let l = ver.ec_info(EcLevel::L); /// // H 级纠错码字更多,数据码字更少 /// assert!(h.ec_per_block > l.ec_per_block); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] pub enum EcLevel { /// L 级 — 约 7% 纠错能力 L, /// M 级 — 约 15% 纠错能力(默认) M, /// Q 级 — 约 25% 纠错能力 Q, /// H 级 — 约 30% 纠错能力 H, } impl EcLevel { /// 格式信息中使用的 2-bit 指示位 pub fn indicator_bits(self) -> u8 { match self { EcLevel::L => 0b01, EcLevel::M => 0b00, EcLevel::Q => 0b11, EcLevel::H => 0b10, } } } /// QR 码版本号(1~40) /// /// 版本决定 QR 码的物理尺寸:`side = 17 + version × 4` 模块。 /// 版本 1 是 21×21,版本 40 是 177×177。 /// /// ```rust /// use qr_core::version::Version; /// /// let v1 = Version::new(1).unwrap(); /// assert_eq!(v1.size(), 21); /// /// let v40 = Version::new(40).unwrap(); /// assert_eq!(v40.size(), 177); /// /// assert!(Version::new(0).is_none()); /// assert!(Version::new(41).is_none()); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] pub struct Version(pub u8); impl Version { pub fn new(v: u8) -> Option { if (1..=40).contains(&v) { Some(Version(v)) } else { None } } /// QR 码边长(模块数) pub fn size(self) -> u8 { 17 + self.0 * 4 } /// 对齐图案位置列表 pub fn alignment_positions(self) -> &'static [u8] { ALIGNMENT_POSITIONS[self.0 as usize - 1] } /// 获取该版本+级别的纠错信息 pub fn ec_info(self, level: EcLevel) -> EcInfo { let row = &VERSION_TABLE[self.0 as usize - 1]; let (total, ec_per_block, g1_blocks, g1_data, g2_blocks, g2_data) = match level { EcLevel::L => ( row.l_total, row.l_ec, row.l_g1, row.l_g1_data, row.l_g2, row.l_g2_data, ), EcLevel::M => ( row.m_total, row.m_ec, row.m_g1, row.m_g1_data, row.m_g2, row.m_g2_data, ), EcLevel::Q => ( row.q_total, row.q_ec, row.q_g1, row.q_g1_data, row.q_g2, row.q_g2_data, ), EcLevel::H => ( row.h_total, row.h_ec, row.h_g1, row.h_g1_data, row.h_g2, row.h_g2_data, ), }; let mut blocks = Vec::new(); if g1_blocks > 0 { blocks.push(BlockInfo { count: g1_blocks, data_codewords: g1_data, }); } if g2_blocks > 0 { blocks.push(BlockInfo { count: g2_blocks, data_codewords: g2_data, }); } EcInfo { total_codewords: total, ec_per_block, blocks, } } } /// 纠错分组信息 /// /// 一组 QR 数据被分为多个块,每个块独立计算 RS 纠错码。 #[derive(Debug, Clone)] pub struct EcInfo { /// 数据码字总数 pub total_codewords: u16, /// 每个块的纠错码字数 pub ec_per_block: u8, /// 分组信息(1~2 组,不同数据码字数) pub blocks: Vec, } /// 单个分组信息 /// /// 所有块的数据码字总数 + 纠错码字总数 = QR 码总容量。 #[derive(Debug, Clone)] pub struct BlockInfo { /// 该组块数 pub count: u16, /// 每个块的数据码字数 pub data_codewords: u16, } /// 单行版本数据 struct VersionRow { l_total: u16, l_ec: u8, l_g1: u16, l_g1_data: u16, l_g2: u16, l_g2_data: u16, m_total: u16, m_ec: u8, m_g1: u16, m_g1_data: u16, m_g2: u16, m_g2_data: u16, q_total: u16, q_ec: u8, q_g1: u16, q_g1_data: u16, q_g2: u16, q_g2_data: u16, h_total: u16, h_ec: u8, h_g1: u16, h_g1_data: u16, h_g2: u16, h_g2_data: u16, } /// ISO 18004 附录 I — 40 版本 × 4 纠错级别的容量表 /// 格式: (总码字数, EC码字/块, 组1块数, 组1数据码字数, 组2块数, 组2数据码字数) /// 当 g2_data == 0 时,表示仅有一组 static VERSION_TABLE: [VersionRow; 40] = [ // 版本 1 VersionRow { l_total: 26, l_ec: 7, l_g1: 1, l_g1_data: 19, l_g2: 0, l_g2_data: 0, m_total: 26, m_ec: 10, m_g1: 1, m_g1_data: 16, m_g2: 0, m_g2_data: 0, q_total: 26, q_ec: 13, q_g1: 1, q_g1_data: 13, q_g2: 0, q_g2_data: 0, h_total: 26, h_ec: 17, h_g1: 1, h_g1_data: 9, h_g2: 0, h_g2_data: 0, }, // 版本 2 VersionRow { l_total: 44, l_ec: 10, l_g1: 1, l_g1_data: 34, l_g2: 0, l_g2_data: 0, m_total: 44, m_ec: 16, m_g1: 1, m_g1_data: 28, m_g2: 0, m_g2_data: 0, q_total: 44, q_ec: 22, q_g1: 1, q_g1_data: 22, q_g2: 0, q_g2_data: 0, h_total: 44, h_ec: 28, h_g1: 1, h_g1_data: 16, h_g2: 0, h_g2_data: 0, }, // 版本 3 VersionRow { l_total: 70, l_ec: 15, l_g1: 1, l_g1_data: 55, l_g2: 0, l_g2_data: 0, m_total: 70, m_ec: 26, m_g1: 1, m_g1_data: 44, m_g2: 0, m_g2_data: 0, q_total: 70, q_ec: 18, q_g1: 2, q_g1_data: 17, q_g2: 0, q_g2_data: 0, h_total: 70, h_ec: 22, h_g1: 2, h_g1_data: 13, h_g2: 0, h_g2_data: 0, }, // 版本 4 VersionRow { l_total: 100, l_ec: 20, l_g1: 1, l_g1_data: 80, l_g2: 0, l_g2_data: 0, m_total: 100, m_ec: 18, m_g1: 2, m_g1_data: 32, m_g2: 0, m_g2_data: 0, q_total: 100, q_ec: 26, q_g1: 2, q_g1_data: 24, q_g2: 0, q_g2_data: 0, h_total: 100, h_ec: 16, h_g1: 4, h_g1_data: 9, h_g2: 0, h_g2_data: 0, }, // 版本 5 VersionRow { l_total: 134, l_ec: 26, l_g1: 1, l_g1_data: 108, l_g2: 0, l_g2_data: 0, m_total: 134, m_ec: 24, m_g1: 2, m_g1_data: 43, m_g2: 0, m_g2_data: 0, q_total: 134, q_ec: 18, q_g1: 2, q_g1_data: 15, q_g2: 2, q_g2_data: 16, h_total: 134, h_ec: 22, h_g1: 2, h_g1_data: 11, h_g2: 2, h_g2_data: 12, }, // 版本 6 VersionRow { l_total: 172, l_ec: 18, l_g1: 2, l_g1_data: 68, l_g2: 0, l_g2_data: 0, m_total: 172, m_ec: 16, m_g1: 4, m_g1_data: 27, m_g2: 0, m_g2_data: 0, q_total: 172, q_ec: 24, q_g1: 4, q_g1_data: 19, q_g2: 0, q_g2_data: 0, h_total: 172, h_ec: 28, h_g1: 4, h_g1_data: 15, h_g2: 0, h_g2_data: 0, }, // 版本 7 VersionRow { l_total: 196, l_ec: 20, l_g1: 2, l_g1_data: 78, l_g2: 0, l_g2_data: 0, m_total: 196, m_ec: 18, m_g1: 4, m_g1_data: 31, m_g2: 0, m_g2_data: 0, q_total: 196, q_ec: 18, q_g1: 2, q_g1_data: 14, q_g2: 4, q_g2_data: 15, h_total: 196, h_ec: 26, h_g1: 4, h_g1_data: 13, h_g2: 1, h_g2_data: 14, }, // 版本 8 VersionRow { l_total: 242, l_ec: 24, l_g1: 2, l_g1_data: 97, l_g2: 0, l_g2_data: 0, m_total: 242, m_ec: 22, m_g1: 2, m_g1_data: 38, m_g2: 2, m_g2_data: 39, q_total: 242, q_ec: 22, q_g1: 4, q_g1_data: 18, q_g2: 2, q_g2_data: 19, h_total: 242, h_ec: 26, h_g1: 4, h_g1_data: 14, h_g2: 2, h_g2_data: 15, }, // 版本 9 VersionRow { l_total: 292, l_ec: 30, l_g1: 2, l_g1_data: 116, l_g2: 0, l_g2_data: 0, m_total: 292, m_ec: 22, m_g1: 3, m_g1_data: 36, m_g2: 2, m_g2_data: 37, q_total: 292, q_ec: 20, q_g1: 4, q_g1_data: 16, q_g2: 4, q_g2_data: 17, h_total: 292, h_ec: 24, h_g1: 4, h_g1_data: 12, h_g2: 4, h_g2_data: 13, }, // 版本 10 VersionRow { l_total: 346, l_ec: 18, l_g1: 2, l_g1_data: 68, l_g2: 2, l_g2_data: 69, m_total: 346, m_ec: 26, m_g1: 4, m_g1_data: 43, m_g2: 1, m_g2_data: 44, q_total: 346, q_ec: 24, q_g1: 6, q_g1_data: 19, q_g2: 2, q_g2_data: 20, h_total: 346, h_ec: 28, h_g1: 6, h_g1_data: 15, h_g2: 2, h_g2_data: 16, }, // 版本 11 VersionRow { l_total: 404, l_ec: 20, l_g1: 4, l_g1_data: 81, l_g2: 0, l_g2_data: 0, m_total: 404, m_ec: 30, m_g1: 1, m_g1_data: 50, m_g2: 4, m_g2_data: 51, q_total: 404, q_ec: 28, q_g1: 4, q_g1_data: 22, q_g2: 4, q_g2_data: 23, h_total: 404, h_ec: 24, h_g1: 3, h_g1_data: 12, h_g2: 8, h_g2_data: 13, }, // 版本 12 VersionRow { l_total: 466, l_ec: 24, l_g1: 2, l_g1_data: 92, l_g2: 2, l_g2_data: 93, m_total: 466, m_ec: 22, m_g1: 6, m_g1_data: 36, m_g2: 2, m_g2_data: 37, q_total: 466, q_ec: 26, q_g1: 4, q_g1_data: 20, q_g2: 6, q_g2_data: 21, h_total: 466, h_ec: 28, h_g1: 7, h_g1_data: 14, h_g2: 4, h_g2_data: 15, }, // 版本 13 VersionRow { l_total: 532, l_ec: 26, l_g1: 4, l_g1_data: 107, l_g2: 0, l_g2_data: 0, m_total: 532, m_ec: 22, m_g1: 8, m_g1_data: 37, m_g2: 1, m_g2_data: 38, q_total: 532, q_ec: 24, q_g1: 8, q_g1_data: 20, q_g2: 4, q_g2_data: 21, h_total: 532, h_ec: 22, h_g1: 12, h_g1_data: 11, h_g2: 4, h_g2_data: 12, }, // 版本 14 VersionRow { l_total: 581, l_ec: 30, l_g1: 3, l_g1_data: 115, l_g2: 1, l_g2_data: 116, m_total: 581, m_ec: 24, m_g1: 4, m_g1_data: 40, m_g2: 5, m_g2_data: 41, q_total: 581, q_ec: 20, q_g1: 11, q_g1_data: 16, q_g2: 5, q_g2_data: 17, h_total: 581, h_ec: 24, h_g1: 11, h_g1_data: 12, h_g2: 5, h_g2_data: 13, }, // 版本 15 VersionRow { l_total: 655, l_ec: 22, l_g1: 5, l_g1_data: 87, l_g2: 1, l_g2_data: 88, m_total: 655, m_ec: 24, m_g1: 5, m_g1_data: 41, m_g2: 5, m_g2_data: 42, q_total: 655, q_ec: 30, q_g1: 5, q_g1_data: 24, q_g2: 7, q_g2_data: 25, h_total: 655, h_ec: 24, h_g1: 11, h_g1_data: 12, h_g2: 7, h_g2_data: 13, }, // 版本 16 VersionRow { l_total: 733, l_ec: 24, l_g1: 5, l_g1_data: 98, l_g2: 1, l_g2_data: 99, m_total: 733, m_ec: 28, m_g1: 7, m_g1_data: 45, m_g2: 3, m_g2_data: 46, q_total: 733, q_ec: 24, q_g1: 15, q_g1_data: 19, q_g2: 2, q_g2_data: 20, h_total: 733, h_ec: 30, h_g1: 3, h_g1_data: 15, h_g2: 13, h_g2_data: 16, }, // 版本 17 VersionRow { l_total: 815, l_ec: 28, l_g1: 1, l_g1_data: 107, l_g2: 5, l_g2_data: 108, m_total: 815, m_ec: 28, m_g1: 10, m_g1_data: 46, m_g2: 1, m_g2_data: 47, q_total: 815, q_ec: 28, q_g1: 1, q_g1_data: 22, q_g2: 15, q_g2_data: 23, h_total: 815, h_ec: 28, h_g1: 2, h_g1_data: 14, h_g2: 17, h_g2_data: 15, }, // 版本 18 VersionRow { l_total: 901, l_ec: 30, l_g1: 5, l_g1_data: 120, l_g2: 1, l_g2_data: 121, m_total: 901, m_ec: 26, m_g1: 9, m_g1_data: 43, m_g2: 4, m_g2_data: 44, q_total: 901, q_ec: 28, q_g1: 17, q_g1_data: 22, q_g2: 1, q_g2_data: 23, h_total: 901, h_ec: 28, h_g1: 2, h_g1_data: 14, h_g2: 19, h_g2_data: 15, }, // 版本 19 VersionRow { l_total: 991, l_ec: 28, l_g1: 3, l_g1_data: 113, l_g2: 4, l_g2_data: 114, m_total: 991, m_ec: 26, m_g1: 3, m_g1_data: 44, m_g2: 11, m_g2_data: 45, q_total: 991, q_ec: 26, q_g1: 17, q_g1_data: 21, q_g2: 4, q_g2_data: 22, h_total: 991, h_ec: 26, h_g1: 9, h_g1_data: 13, h_g2: 16, h_g2_data: 14, }, // 版本 20 VersionRow { l_total: 1085, l_ec: 28, l_g1: 3, l_g1_data: 107, l_g2: 5, l_g2_data: 108, m_total: 1085, m_ec: 26, m_g1: 3, m_g1_data: 41, m_g2: 13, m_g2_data: 42, q_total: 1085, q_ec: 30, q_g1: 15, q_g1_data: 24, q_g2: 5, q_g2_data: 25, h_total: 1085, h_ec: 28, h_g1: 15, h_g1_data: 15, h_g2: 10, h_g2_data: 16, }, // 版本 21 VersionRow { l_total: 1156, l_ec: 28, l_g1: 4, l_g1_data: 116, l_g2: 4, l_g2_data: 117, m_total: 1156, m_ec: 26, m_g1: 17, m_g1_data: 42, m_g2: 0, m_g2_data: 0, q_total: 1156, q_ec: 28, q_g1: 17, q_g1_data: 22, q_g2: 6, q_g2_data: 23, h_total: 1156, h_ec: 30, h_g1: 19, h_g1_data: 16, h_g2: 6, h_g2_data: 17, }, // 版本 22 VersionRow { l_total: 1258, l_ec: 28, l_g1: 2, l_g1_data: 111, l_g2: 7, l_g2_data: 112, m_total: 1258, m_ec: 28, m_g1: 17, m_g1_data: 46, m_g2: 0, m_g2_data: 0, q_total: 1258, q_ec: 30, q_g1: 7, q_g1_data: 24, q_g2: 16, q_g2_data: 25, h_total: 1258, h_ec: 24, h_g1: 34, h_g1_data: 13, h_g2: 0, h_g2_data: 0, }, // 版本 23 VersionRow { l_total: 1364, l_ec: 30, l_g1: 4, l_g1_data: 121, l_g2: 5, l_g2_data: 122, m_total: 1364, m_ec: 28, m_g1: 4, m_g1_data: 47, m_g2: 14, m_g2_data: 48, q_total: 1364, q_ec: 30, q_g1: 11, q_g1_data: 24, q_g2: 14, q_g2_data: 25, h_total: 1364, h_ec: 30, h_g1: 16, h_g1_data: 15, h_g2: 14, h_g2_data: 16, }, // 版本 24 VersionRow { l_total: 1474, l_ec: 30, l_g1: 6, l_g1_data: 117, l_g2: 4, l_g2_data: 118, m_total: 1474, m_ec: 28, m_g1: 6, m_g1_data: 45, m_g2: 14, m_g2_data: 46, q_total: 1474, q_ec: 30, q_g1: 11, q_g1_data: 24, q_g2: 16, q_g2_data: 25, h_total: 1474, h_ec: 30, h_g1: 30, h_g1_data: 16, h_g2: 2, h_g2_data: 17, }, // 版本 25 VersionRow { l_total: 1588, l_ec: 26, l_g1: 8, l_g1_data: 106, l_g2: 4, l_g2_data: 107, m_total: 1588, m_ec: 28, m_g1: 8, m_g1_data: 47, m_g2: 13, m_g2_data: 48, q_total: 1588, q_ec: 30, q_g1: 7, q_g1_data: 24, q_g2: 22, q_g2_data: 25, h_total: 1588, h_ec: 30, h_g1: 22, h_g1_data: 15, h_g2: 13, h_g2_data: 16, }, // 版本 26 VersionRow { l_total: 1706, l_ec: 28, l_g1: 10, l_g1_data: 114, l_g2: 2, l_g2_data: 115, m_total: 1706, m_ec: 28, m_g1: 19, m_g1_data: 46, m_g2: 4, m_g2_data: 47, q_total: 1706, q_ec: 28, q_g1: 28, q_g1_data: 22, q_g2: 6, q_g2_data: 23, h_total: 1706, h_ec: 30, h_g1: 33, h_g1_data: 16, h_g2: 4, h_g2_data: 17, }, // 版本 27 VersionRow { l_total: 1828, l_ec: 30, l_g1: 8, l_g1_data: 122, l_g2: 4, l_g2_data: 123, m_total: 1828, m_ec: 28, m_g1: 22, m_g1_data: 45, m_g2: 3, m_g2_data: 46, q_total: 1828, q_ec: 30, q_g1: 8, q_g1_data: 23, q_g2: 26, q_g2_data: 24, h_total: 1828, h_ec: 30, h_g1: 12, h_g1_data: 15, h_g2: 28, h_g2_data: 16, }, // 版本 28 VersionRow { l_total: 1921, l_ec: 30, l_g1: 3, l_g1_data: 117, l_g2: 10, l_g2_data: 118, m_total: 1921, m_ec: 28, m_g1: 3, m_g1_data: 45, m_g2: 23, m_g2_data: 46, q_total: 1921, q_ec: 30, q_g1: 4, q_g1_data: 24, q_g2: 31, q_g2_data: 25, h_total: 1921, h_ec: 30, h_g1: 11, h_g1_data: 15, h_g2: 31, h_g2_data: 16, }, // 版本 29 VersionRow { l_total: 2051, l_ec: 30, l_g1: 7, l_g1_data: 116, l_g2: 7, l_g2_data: 117, m_total: 2051, m_ec: 28, m_g1: 21, m_g1_data: 45, m_g2: 7, m_g2_data: 46, q_total: 2051, q_ec: 30, q_g1: 1, q_g1_data: 23, q_g2: 37, q_g2_data: 24, h_total: 2051, h_ec: 30, h_g1: 19, h_g1_data: 15, h_g2: 26, h_g2_data: 16, }, // 版本 30 VersionRow { l_total: 2185, l_ec: 30, l_g1: 5, l_g1_data: 115, l_g2: 10, l_g2_data: 116, m_total: 2185, m_ec: 28, m_g1: 19, m_g1_data: 47, m_g2: 10, m_g2_data: 48, q_total: 2185, q_ec: 30, q_g1: 15, q_g1_data: 24, q_g2: 25, q_g2_data: 25, h_total: 2185, h_ec: 30, h_g1: 23, h_g1_data: 15, h_g2: 25, h_g2_data: 16, }, // 版本 31 VersionRow { l_total: 2323, l_ec: 30, l_g1: 13, l_g1_data: 115, l_g2: 3, l_g2_data: 116, m_total: 2323, m_ec: 28, m_g1: 2, m_g1_data: 46, m_g2: 29, m_g2_data: 47, q_total: 2323, q_ec: 30, q_g1: 42, q_g1_data: 24, q_g2: 1, q_g2_data: 25, h_total: 2323, h_ec: 30, h_g1: 23, h_g1_data: 15, h_g2: 28, h_g2_data: 16, }, // 版本 32 VersionRow { l_total: 2465, l_ec: 30, l_g1: 17, l_g1_data: 115, l_g2: 0, l_g2_data: 0, m_total: 2465, m_ec: 28, m_g1: 10, m_g1_data: 46, m_g2: 23, m_g2_data: 47, q_total: 2465, q_ec: 30, q_g1: 10, q_g1_data: 24, q_g2: 35, q_g2_data: 25, h_total: 2465, h_ec: 30, h_g1: 19, h_g1_data: 15, h_g2: 35, h_g2_data: 16, }, // 版本 33 VersionRow { l_total: 2611, l_ec: 30, l_g1: 17, l_g1_data: 115, l_g2: 1, l_g2_data: 116, m_total: 2611, m_ec: 28, m_g1: 14, m_g1_data: 46, m_g2: 21, m_g2_data: 47, q_total: 2611, q_ec: 30, q_g1: 29, q_g1_data: 24, q_g2: 19, q_g2_data: 25, h_total: 2611, h_ec: 30, h_g1: 11, h_g1_data: 15, h_g2: 46, h_g2_data: 16, }, // 版本 34 VersionRow { l_total: 2761, l_ec: 30, l_g1: 13, l_g1_data: 115, l_g2: 6, l_g2_data: 116, m_total: 2761, m_ec: 28, m_g1: 14, m_g1_data: 46, m_g2: 23, m_g2_data: 47, q_total: 2761, q_ec: 30, q_g1: 44, q_g1_data: 24, q_g2: 7, q_g2_data: 25, h_total: 2761, h_ec: 30, h_g1: 59, h_g1_data: 16, h_g2: 1, h_g2_data: 17, }, // 版本 35 VersionRow { l_total: 2876, l_ec: 30, l_g1: 12, l_g1_data: 121, l_g2: 7, l_g2_data: 122, m_total: 2876, m_ec: 28, m_g1: 12, m_g1_data: 47, m_g2: 26, m_g2_data: 48, q_total: 2876, q_ec: 30, q_g1: 39, q_g1_data: 24, q_g2: 14, q_g2_data: 25, h_total: 2876, h_ec: 30, h_g1: 22, h_g1_data: 15, h_g2: 41, h_g2_data: 16, }, // 版本 36 VersionRow { l_total: 3034, l_ec: 30, l_g1: 6, l_g1_data: 121, l_g2: 14, l_g2_data: 122, m_total: 3034, m_ec: 28, m_g1: 6, m_g1_data: 47, m_g2: 34, m_g2_data: 48, q_total: 3034, q_ec: 30, q_g1: 46, q_g1_data: 24, q_g2: 10, q_g2_data: 25, h_total: 3034, h_ec: 30, h_g1: 2, h_g1_data: 15, h_g2: 64, h_g2_data: 16, }, // 版本 37 VersionRow { l_total: 3196, l_ec: 30, l_g1: 17, l_g1_data: 122, l_g2: 4, l_g2_data: 123, m_total: 3196, m_ec: 28, m_g1: 29, m_g1_data: 46, m_g2: 14, m_g2_data: 47, q_total: 3196, q_ec: 30, q_g1: 49, q_g1_data: 24, q_g2: 10, q_g2_data: 25, h_total: 3196, h_ec: 30, h_g1: 24, h_g1_data: 15, h_g2: 46, h_g2_data: 16, }, // 版本 38 VersionRow { l_total: 3362, l_ec: 30, l_g1: 4, l_g1_data: 122, l_g2: 18, l_g2_data: 123, m_total: 3362, m_ec: 28, m_g1: 13, m_g1_data: 46, m_g2: 32, m_g2_data: 47, q_total: 3362, q_ec: 30, q_g1: 48, q_g1_data: 24, q_g2: 14, q_g2_data: 25, h_total: 3362, h_ec: 30, h_g1: 42, h_g1_data: 15, h_g2: 32, h_g2_data: 16, }, // 版本 39 VersionRow { l_total: 3532, l_ec: 30, l_g1: 20, l_g1_data: 117, l_g2: 4, l_g2_data: 118, m_total: 3532, m_ec: 28, m_g1: 40, m_g1_data: 47, m_g2: 7, m_g2_data: 48, q_total: 3532, q_ec: 30, q_g1: 43, q_g1_data: 24, q_g2: 22, q_g2_data: 25, h_total: 3532, h_ec: 30, h_g1: 10, h_g1_data: 15, h_g2: 67, h_g2_data: 16, }, // 版本 40 VersionRow { l_total: 3706, l_ec: 30, l_g1: 19, l_g1_data: 118, l_g2: 6, l_g2_data: 119, m_total: 3706, m_ec: 28, m_g1: 18, m_g1_data: 47, m_g2: 31, m_g2_data: 48, q_total: 3706, q_ec: 30, q_g1: 34, q_g1_data: 24, q_g2: 34, q_g2_data: 25, h_total: 3706, h_ec: 30, h_g1: 20, h_g1_data: 15, h_g2: 61, h_g2_data: 16, }, ]; // 对齐图案中心位置表(每个版本的坐标数组) static ALIGNMENT_POSITIONS: [&[u8]; 40] = [ &[], // v1 (无对齐图案) &[6, 18], // v2 &[6, 22], // v3 &[6, 26], // v4 &[6, 30], // v5 &[6, 34], // v6 &[6, 22, 38], // v7 &[6, 24, 42], // v8 &[6, 26, 46], // v9 &[6, 28, 50], // v10 &[6, 30, 54], // v11 &[6, 32, 58], // v12 &[6, 34, 62], // v13 &[6, 26, 46, 66], // v14 &[6, 26, 48, 70], // v15 &[6, 26, 50, 74], // v16 &[6, 30, 54, 78], // v17 &[6, 30, 56, 82], // v18 &[6, 30, 58, 86], // v19 &[6, 34, 62, 90], // v20 &[6, 28, 50, 72, 94], // v21 &[6, 26, 50, 74, 98], // v22 &[6, 30, 54, 78, 102], // v23 &[6, 28, 54, 80, 106], // v24 &[6, 32, 58, 84, 110], // v25 &[6, 30, 58, 86, 114], // v26 &[6, 34, 62, 90, 118], // v27 &[6, 26, 50, 74, 98, 122], // v28 &[6, 30, 54, 78, 102, 126], // v29 &[6, 26, 52, 78, 104, 130], // v30 &[6, 30, 56, 82, 108, 134], // v31 &[6, 34, 60, 86, 112, 138], // v32 &[6, 30, 58, 86, 114, 142], // v33 &[6, 34, 62, 90, 118, 146], // v34 &[6, 30, 54, 78, 102, 126, 150], // v35 &[6, 24, 50, 76, 102, 128, 154], // v36 &[6, 28, 54, 80, 106, 132, 158], // v37 &[6, 32, 58, 84, 110, 136, 162], // v38 &[6, 26, 54, 82, 110, 138, 166], // v39 &[6, 30, 58, 86, 114, 142, 170], // v40 ]; /// 获取某版本+级别的数据码字容量 fn init_capacity_table() -> [[u16; 4]; 40] { let mut table = [[0u16; 4]; 40]; for v in 1..=40u8 { let ver = Version(v); let li: [EcLevel; 4] = [EcLevel::L, EcLevel::M, EcLevel::Q, EcLevel::H]; for (idx, &level) in li.iter().enumerate() { let info = ver.ec_info(level); let total_data: u16 = info.blocks.iter().map(|b| b.count * b.data_codewords).sum(); table[v as usize - 1][idx] = total_data; } } table } /// 查询指定版本+纠错级别的数据码字容量 /// /// 返回数据码字数(非比特数),乘以 8 得比特容量。 /// /// ```rust /// use qr_core::version::{get_data_capacity, Version, EcLevel}; /// /// let cap = get_data_capacity(Version(1), EcLevel::M); /// assert_eq!(cap, 16); // 版本 1-M 有 16 个数据码字 /// ``` // SAFETY: version.0 ∈ [1,40] 由 Version::new() 保证; level 是 4 变体枚举 #[allow(clippy::indexing_slicing)] pub fn get_data_capacity(version: Version, level: EcLevel) -> u16 { static CAPACITY: OnceLock<[[u16; 4]; 40]> = OnceLock::new(); let cap = CAPACITY.get_or_init(init_capacity_table); cap[version.0 as usize - 1][level as usize] } /// 自动选择能容纳 `data_bits` 比特的最小版本 /// /// 返回 `None` 表示数据量超出 QR 码最大容量(版本 40)。 /// /// ```rust /// use qr_core::version::{pick_version, EcLevel}; /// /// let v = pick_version(100, EcLevel::M).unwrap(); /// assert_eq!(v.0, 1); // 版本 1-M 可容纳 16×8=128 bit >= 100 /// /// let too_big = pick_version(50_000, EcLevel::H); /// assert!(too_big.is_none()); /// ``` pub fn pick_version(data_bits: u16, level: EcLevel) -> Option { for v in 1..=40 { let cap_bits = get_data_capacity(Version(v), level) * 8; if cap_bits >= data_bits { return Some(Version(v)); } } None } #[cfg(test)] mod tests { use super::*; #[test] fn test_version_size() { assert_eq!(Version(1).size(), 21); assert_eq!(Version(40).size(), 177); assert_eq!(Version(2).size(), 25); } #[test] fn test_version_new_out_of_range() { assert!(Version::new(0).is_none()); assert!(Version::new(41).is_none()); } #[test] fn test_pick_version_l() { // Version 1 L 级: 19 数据码字 = 152 bits let v = pick_version(152, EcLevel::L); assert_eq!(v, Some(Version(1))); } #[test] fn test_pick_version_none() { // 超过 version 40 容量 let v = pick_version(30000, EcLevel::H); assert!(v.is_none()); } #[test] fn test_ec_info_blocks() { let info = Version(1).ec_info(EcLevel::M); // Version 1 M: 1 block × 16 data codewords, 10 ec per block // g2 组 count=0 已被过滤,仅保留 g1 assert_eq!(info.total_codewords, 26); assert_eq!(info.ec_per_block, 10); assert_eq!(info.blocks.len(), 1); assert_eq!(info.blocks[0].count, 1); assert_eq!(info.blocks[0].data_codewords, 16); } #[test] fn test_indicator_bits() { assert_eq!(EcLevel::L.indicator_bits(), 0b01); assert_eq!(EcLevel::M.indicator_bits(), 0b00); assert_eq!(EcLevel::Q.indicator_bits(), 0b11); assert_eq!(EcLevel::H.indicator_bits(), 0b10); } }