ce8063431e
- 为 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
1428 lines
30 KiB
Rust
1428 lines
30 KiB
Rust
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);
|
||
}
|
||
}
|