Files
QRGen/core/src/version.rs
T
Serendipity ce8063431e docs: 开源规范化 — doc comments + 社区文件 + 示例代码 + crates.io 就绪
- 为 core 公开 API 添加完整 doc comments(rustdoc 可用)
- 新增 .editorconfig / CONTRIBUTING / CODE_OF_CONDUCT / SECURITY
- 新增 Issue 模板(bug + feature)+ PR 模板
- 新增 3 个代码示例(examples/)
- 更新 Cargo.toml 元数据(description/repository/keywords/categories/MSRV)
- 更新 README + CHANGELOG
2026-06-19 18:56:28 +08:00

1428 lines
30 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<Self> {
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<BlockInfo>,
}
/// 单个分组信息
///
/// 所有块的数据码字总数 + 纠错码字总数 = 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<Version> {
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);
}
}