Files
QRGen/docs/superpowers/plans/2026-06-16-qrcode-generator-plan.md

2423 lines
65 KiB
Markdown
Raw Permalink 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.
# QR 码生成器 (QRGen) 实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 从零实现 ISO/IEC 18004 QR 码生成器,Rust workspace (core + cli),完整算法手写
**Architecture:** Workspace 结构 `core/` (纯算法库) + `cli/` (命令行工具)。`core` 按数据流水线分层:Galois → RS → 编码 → 矩阵 → 渲染
**Tech Stack:** Rust 2021 edition, `image` crate (PNG), `clap` (CLI parsing), `anyhow` (error handling)
---
## 文件清单
| 文件 | 职责 | 行数估算 |
|------|------|---------|
| `core/src/ecc/galois.rs` | GF(2⁸) 运算 + exp/log 预计算表 | ~80 |
| `core/src/ecc/reed_solomon.rs` | RS 纠错码生成 | ~80 |
| `core/src/version.rs` | 版本容量表 + 查找 | ~120 |
| `core/src/encoder/mode.rs` | 四种编码模式实现 | ~150 |
| `core/src/encoder/segment.rs` | 字符串分析 + 分段 | ~100 |
| `core/src/encoder/bitstream.rs` | 比特流拼接 | ~130 |
| `core/src/matrix/grid.rs` | 模块矩阵数据结构 | ~40 |
| `core/src/matrix/patterns.rs` | 定位/对齐/时序图案 | ~100 |
| `core/src/matrix/placement.rs` | 蛇形数据排列 | ~150 |
| `core/src/matrix/mask.rs` | 8 种掩码 + 四规则评分 | ~130 |
| `core/src/render/png.rs` | PNG 渲染 | ~50 |
| `core/src/render/svg.rs` | SVG 渲染 | ~50 |
| `core/src/render/ascii.rs` | ASCII 渲染 | ~50 |
| `core/src/qr.rs` | 顶层 APIencode + 输出 | ~120 |
| `cli/src/main.rs` | CLI 入口 (clap) | ~80 |
---
### Task 1: 搭建 Workspace 骨架
**Files:**
- Create: `QRGen/Cargo.toml`
- Create: `QRGen/core/Cargo.toml`
- Create: `QRGen/core/src/lib.rs`
- Create: `QRGen/cli/Cargo.toml`
- Create: `QRGen/cli/src/main.rs`
- Create: `QRGen/core/src/ecc/mod.rs`
- Create: `QRGen/core/src/encoder/mod.rs`
- Create: `QRGen/core/src/matrix/mod.rs`
- Create: `QRGen/core/src/render/mod.rs`
- [ ] **Step 1: 创建 workspace 根 Cargo.toml**
```toml
[workspace]
resolver = "2"
members = ["core", "cli"]
[workspace.package]
version = "0.1.0"
edition = "2021"
license = "MIT"
authors = ["刘航宇"]
```
- [ ] **Step 2: 创建 core/Cargo.toml**
```toml
[package]
name = "qr-core"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
image = { version = "0.25", default-features = false, features = ["png"] }
[dev-dependencies]
```
- [ ] **Step 3: 创建 cli/Cargo.toml**
```toml
[package]
name = "qrgen"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
[[bin]]
name = "qrgen"
path = "src/main.rs"
[dependencies]
qr-core = { path = "../core" }
clap = { version = "4", features = ["derive"] }
anyhow = "1"
```
- [ ] **Step 4: 创建各模块的空 mod.rs 文件**
`core/src/lib.rs`:
```rust
pub mod ecc;
pub mod encoder;
pub mod matrix;
pub mod render;
pub mod version;
pub mod qr;
```
`core/src/ecc/mod.rs`:
```rust
pub mod galois;
pub mod reed_solomon;
```
`core/src/encoder/mod.rs`:
```rust
pub mod mode;
pub mod segment;
pub mod bitstream;
```
`core/src/matrix/mod.rs`:
```rust
pub mod grid;
pub mod patterns;
pub mod placement;
pub mod mask;
```
`core/src/render/mod.rs`:
```rust
pub mod png;
pub mod svg;
pub mod ascii;
```
`cli/src/main.rs`:
```rust
fn main() {
println!("QRGen - 开发中");
}
```
- [ ] **Step 5: 验证编译**
```bash
cd D:\Code\doing_exercises\programs\QRGen && cargo build
```
---
### Task 2: GF(2⁸) Galois 域
**Files:**
- Create: `QRGen/core/src/ecc/galois.rs`
- [ ] **Step 1: 实现 Galois 域运算**
```rust
/// GF(2⁸) Galois 域运算
/// 本原多项式: x⁸ + x⁴ + x³ + x² + 1 = 0x11D
/// 生成元 α = 0x02
/// 预计算的 exp 表: exp[i] = αⁱ
static EXP_TABLE: [u8; 512] = {
let mut table = [0u8; 512];
let mut x = 1u8;
let mut i = 0;
while i < 255 {
table[i] = x;
table[i + 255] = x; // 双倍长度避免 % 255
x = if (x << 1) >= 0x100 {
(x << 1) ^ 0x1D // x⁸ 溢出,减去本原多项式
} else {
x << 1
};
i += 1;
}
table[255 + 255] = table[255]; // 额外填充
table
};
/// 预计算的 log 表: log[x] = i 使得 αⁱ = x (x > 0)
static LOG_TABLE: [u8; 256] = {
let mut table = [0u8; 256];
// EXP_TABLE 此时已初始化
let mut i = 0usize;
while i < 255 {
// 需要运行时计算,这里用函数做
i += 1;
}
table
};
/// GF(2⁸) 加法 = 异或
#[inline]
pub fn add(a: u8, b: u8) -> u8 {
a ^ b
}
/// GF(2⁸) 减法 = 异或(同加法)
#[inline]
pub fn sub(a: u8, b: u8) -> u8 {
a ^ b
}
/// GF(2⁸) 乘法
pub fn mul(a: u8, b: u8) -> u8 {
if a == 0 || b == 0 {
return 0;
}
let log_a = LOG_TABLE[a as usize] as usize;
let log_b = LOG_TABLE[b as usize] as usize;
EXP_TABLE[log_a + log_b]
}
/// GF(2⁸) 除法
pub fn div(a: u8, b: u8) -> u8 {
if a == 0 {
return 0;
}
if b == 0 {
panic!("GF(2⁸) 除以零");
}
let log_a = LOG_TABLE[a as usize] as usize;
let log_b = LOG_TABLE[b as usize] as usize;
let diff = (log_a + 255 - log_b) % 255;
EXP_TABLE[diff]
}
/// GF(2⁸) 幂运算
pub fn pow(base: u8, exp: usize) -> u8 {
if exp == 0 {
return 1;
}
if base == 0 {
return 0;
}
let log_b = LOG_TABLE[base as usize] as usize;
EXP_TABLE[(log_b * exp) % 255]
}
/// 懒初始化 log 表
fn init_log_table() -> [u8; 256] {
let mut table = [0u8; 256];
let mut x = 1u8;
for i in 0..255 {
table[x as usize] = i;
x = if (x << 1) >= 0x100 {
(x << 1) ^ 0x1D
} else {
x << 1
};
}
table
}
```
`EXP_TABLE``LOG_TABLE` 不能用 `const` + 运行时计算,Rust 的 const fn 限制太多。需要改用 `once_cell::sync::Lazy` 或手工初始化。
修正方案 — 使用函数在首次调用时初始化:
```rust
use std::sync::OnceLock;
fn exp_table() -> &'static [u8; 512] {
static TABLE: OnceLock<[u8; 512]> = OnceLock::new();
TABLE.get_or_init(|| {
let mut table = [0u8; 512];
let mut x = 1u8;
for i in 0..255 {
table[i] = x;
table[i + 255] = x;
x = if (x << 1) >= 0x100 {
(x << 1) ^ 0x1D
} else {
x << 1
};
}
table[510] = table[255];
table[511] = table[256];
table
})
}
fn log_table() -> &'static [u8; 256] {
static TABLE: OnceLock<[u8; 256]> = OnceLock::new();
TABLE.get_or_init(|| {
let mut table = [0u8; 256];
let mut x = 1u8;
for i in 0..255 {
table[x as usize] = i;
x = if (x << 1) >= 0x100 {
(x << 1) ^ 0x1D
} else {
x << 1
};
}
table
})
}
```
- [ ] **Step 2: 添加测试**
`galois.rs` 末尾添加:
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mul_basic() {
// 2 * 3 = 6 in GF(2⁸)
assert_eq!(mul(2, 3), 6);
// 0xFF * 0x01 = 0xFF
assert_eq!(mul(0xFF, 1), 0xFF);
// 任何数乘 0 = 0
assert_eq!(mul(0xA5, 0), 0);
}
#[test]
fn test_mul_commutative() {
for a in 0..=255u8 {
for b in (a..=255u8).step_by(17) {
assert_eq!(mul(a, b), mul(b, a),
"交换律失败: {:02X} * {:02X}", a, b);
}
}
}
#[test]
fn test_mul_associative() {
let cases = [(3, 5, 7), (0xFF, 2, 4), (1, 1, 1), (0x80, 0x40, 0x20)];
for (a, b, c) in cases {
assert_eq!(mul(mul(a, b), c), mul(a, mul(b, c)),
"结合律失败: {:02X} * {:02X} * {:02X}", a, b, c);
}
}
#[test]
fn test_div_inverse() {
for a in 1..=255u8 {
let inv = div(1, a);
assert_eq!(mul(a, inv), 1,
"逆元失败: {:02X} * {:02X} != 1", a, inv);
}
}
#[test]
fn test_div_mul_consistency() {
for a in 1..=255u8 {
for b in (1..=255u8).step_by(17) {
let q = div(a, b);
assert_eq!(mul(q, b), a,
"除乘一致性失败: {:02X} / {:02X} = {:02X}", a, b, q);
}
}
}
#[test]
fn test_add_sub() {
for a in 0..=255u8 {
for b in 0..=255u8 {
assert_eq!(add(a, b), sub(a, b));
assert_eq!(sub(add(a, b), b), a);
}
}
}
#[test]
fn test_pow() {
// α⁰ = 1
assert_eq!(pow(2, 0), 1);
// α¹ = 2
assert_eq!(pow(2, 1), 2);
// α⁷ * α² = α⁹ — 用 mul 验证
assert_eq!(mul(pow(2, 7), pow(2, 2)), pow(2, 9));
}
}
```
- [ ] **Step 3: 运行测试验证**
```bash
cd D:\Code\doing_exercises\programs\QRGen && cargo test -p qr-core ecc::galois
```
Expected: 7 tests pass
- [ ] **Step 4: 提交**
```bash
git add . && git commit -m "feat: GF(2^8) Galois 域运算 + 预计算 exp/log 表"
```
---
### Task 3: 版本参数表
**Files:**
- Create: `QRGen/core/src/version.rs`
- [ ] **Step 1: 实现版本容量表**
```rust
/// 纠错级别
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EcLevel {
L, // 约 7%
M, // 约 15%
Q, // 约 25%
H, // 约 30%
}
impl EcLevel {
/// 格式信息中用到的指示位
pub fn indicator_bits(self) -> u8 {
match self {
EcLevel::L => 0b01,
EcLevel::M => 0b00,
EcLevel::Q => 0b11,
EcLevel::H => 0b10,
}
}
}
/// 版本号 1~40
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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]
}
/// 获取该版本+级别的纠错信息:
/// (每组码字数, EC码字数, 组数1, 组1码字数, 组数2, 组2码字数)
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),
};
EcInfo {
total_codewords: total,
ec_per_block,
blocks: vec![
BlockInfo { count: g1_blocks, data_codewords: g1_data },
BlockInfo { count: g2_blocks, data_codewords: g2_data },
],
}
}
}
pub struct EcInfo {
pub total_codewords: u16,
pub ec_per_block: u8,
pub blocks: Vec<BlockInfo>,
}
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_per_block, 组1数量, 组1数据码字数, 组2数量, 组2数据码字数)
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, m_g2: 0, m_g2_data: 0,
},
// ... (后续版本 3-40,数据来自 ISO 18004 表 7/8/9)
// 版本 3
// 版本 4
// ...
// 版本 40
];
// 对齐图案中心位置表
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);
for (li, level) in [EcLevel::L, EcLevel::M, EcLevel::Q, EcLevel::H].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][li] = total_data;
}
}
table
}
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]
}
/// 自动选择最小版本
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 // 数据太长,超过 version 40 容量
}
```
注意:版本 3-40 的容量表数据需要从 ISO 18004 标准附录中完整填充。这里先留出结构。
- [ ] **Step 2: 添加版本查找测试**
```rust
#[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)));
}
}
```
- [ ] **Step 3: 运行测试**
```bash
cd D:\Code\doing_exercises\programs\QRGen && cargo test -p qr-core version
```
- [ ] **Step 4: 提交**
```bash
git add . && git commit -m "feat: 版本参数表 + 自动版本选择"
```
---
### Task 4: Reed-Solomon 编码器
**Files:**
- Create: `QRGen/core/src/ecc/reed_solomon.rs`
- [ ] **Step 1: 实现 RS 编码**
```rust
use crate::ecc::galois;
/// 计算多项式相乘: a(x) * b(x) in GF(2⁸)
fn poly_mul(a: &[u8], b: &[u8]) -> Vec<u8> {
let mut result = vec![0u8; a.len() + b.len() - 1];
for (i, &ai) in a.iter().enumerate() {
for (j, &bj) in b.iter().enumerate() {
result[i + j] = galois::add(result[i + j], galois::mul(ai, bj));
}
}
result
}
/// 构造 RS 生成多项式: ∏ᵢ₌₀ⁿ⁻¹ (x - αⁱ)
/// 参数 n: 纠错码字数
fn generator_polynomial(n: u8) -> Vec<u8> {
let mut g = vec![1u8]; // 从 g(x) = 1 开始
for i in 0..n {
// g(x) *= (x - αⁱ) = (x + αⁱ) in GF(2⁸)
let factor = vec![1u8, galois::pow(2, i as usize)];
g = poly_mul(&g, &factor);
}
g
}
/// 多项式长除法: message(x) * xⁿ ÷ generator(x),返回余数(即纠错码字)
/// message: 数据码字
/// ec_count: 纠错码字数量
pub fn compute_ec(data: &[u8], ec_count: u8) -> Vec<u8> {
let gen = generator_polynomial(ec_count);
// 构造被除数: data * x^ec_count
let mut dividend = vec![0u8; data.len() + ec_count as usize];
dividend[..data.len()].copy_from_slice(data);
// 多项式长除法
for i in 0..data.len() {
let coef = dividend[i];
if coef != 0 {
for (j, &g) in gen.iter().enumerate() {
dividend[i + j] = galois::add(dividend[i + j], galois::mul(coef, g));
}
}
}
// 余数 = 最后 ec_count 个系数
dividend[data.len()..].to_vec()
}
/// 对一组数据块生成纠错码字并交错排列
/// blocks: 每个数据块的内容
/// ec_count: 每块纠错码字数
/// 返回: 数据码字交错 + 纠错码字交错
pub fn interleave(blocks: &[Vec<u8>], ec_count: u8) -> Vec<u8> {
// 数据码字交错:取每块第1个,再取每块第2个...
let max_data = blocks.iter().map(|b| b.len()).max().unwrap_or(0);
let mut result = Vec::new();
for i in 0..max_data {
for block in blocks.iter() {
if i < block.len() {
result.push(block[i]);
}
}
}
// 对每个块计算 EC
let ec_blocks: Vec<Vec<u8>> = blocks.iter()
.map(|b| compute_ec(b, ec_count))
.collect();
// EC 码字交错
for i in 0..ec_count as usize {
for ec in ec_blocks.iter() {
result.push(ec[i]);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generator_polynomial_degree() {
let g = generator_polynomial(7);
// 7 个纠错码字 → 生成多项式应为 7 次,共 8 个系数
assert_eq!(g.len(), 8);
}
#[test]
fn test_compute_ec_known() {
// 测试数据来自 ISO 18004 附录示例
let data = vec![
0x10, 0x20, 0x0C, 0x56, 0x61, 0x80, 0xEC, 0x11,
0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
0xA3, 0x12, 0xD3
];
let ec = compute_ec(&data, 7);
// EC 码字应有 7 个
assert_eq!(ec.len(), 7);
// 验证内容(可根据已知正确值检查)
// 期望: [0xD8, 0x8A, 0x87, 0x3E, 0xDF, 0x4B, 0xCE]
assert_eq!(ec, vec![0xD8, 0x8A, 0x87, 0x3E, 0xDF, 0x4B, 0xCE]);
}
#[test]
fn test_ec_corrects_zero_data() {
// 全零数据 + EC → EC 应全零
let ec = compute_ec(&vec![0u8; 19], 7);
assert_eq!(ec, vec![0u8; 7]);
}
#[test]
fn test_interleave() {
let b1 = vec![1, 2, 3];
let b2 = vec![4, 5];
let result = interleave(&[b1, b2], 2);
// 数据交错: 1, 4, 2, 5, 3
// EC 交错: b1_ec[0], b2_ec[0], b1_ec[1], b2_ec[1]
assert_eq!(&result[..5], &[1, 4, 2, 5, 3]);
}
}
```
- [ ] **Step 2: 运行测试**
```bash
cd D:\Code\doing_exercises\programs\QRGen && cargo test -p qr-core ecc::reed_solomon
```
- [ ] **Step 3: 提交**
```bash
git add . && git commit -m "feat: Reed-Solomon 纠错编码 + 数据交错"
```
---
### Task 5: 编码模式实现
**Files:**
- Create: `QRGen/core/src/encoder/mode.rs`
- [ ] **Step 1: 实现四种编码模式**
```rust
/// 编码模式
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
Numeric,
Alphanumeric,
Byte,
Kanji,
}
impl Mode {
/// 模式指示符(4 bit,标准规定)
pub fn indicator(self) -> u8 {
match self {
Mode::Numeric => 0b0001,
Mode::Alphanumeric => 0b0010,
Mode::Byte => 0b0100,
Mode::Kanji => 0b1000,
}
}
/// 字符计数指示符长度(bit
pub fn count_bits(self, version: u8) -> u8 {
match self {
Mode::Numeric => {
if version <= 9 { 10 } else if version <= 26 { 12 } else { 14 }
}
Mode::Alphanumeric => {
if version <= 9 { 9 } else if version <= 26 { 11 } else { 13 }
}
Mode::Byte => {
if version <= 9 { 8 } else { 16 }
}
Mode::Kanji => {
if version <= 9 { 8 } else if version <= 26 { 10 } else { 12 }
}
}
}
}
/// 数字模式编码: 每 3 位数字 → 10 bit
pub fn encode_numeric(input: &str) -> Vec<bool> {
let mut bits = Vec::new();
let chars: Vec<u8> = input.chars()
.filter_map(|c| c.to_digit(10).map(|d| d as u8))
.collect();
for chunk in chars.chunks(3) {
let s: String = chunk.iter().map(|d| (b'0' + d) as char).collect();
let val: u16 = s.parse().unwrap_or(0);
let bit_width = match chunk.len() {
3 => 10,
2 => 7,
1 => 4,
_ => 0,
};
for i in (0..bit_width).rev() {
bits.push((val >> i) & 1 == 1);
}
}
bits
}
/// 字母数字模式字符集
const ALPHANUMERIC_CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
/// 字母数字模式编码: 每 2 个字符 → 11 bit
pub fn encode_alphanumeric(input: &str) -> Vec<bool> {
let values: Vec<u8> = input.chars()
.filter_map(|c| {
ALPHANUMERIC_CHARS.iter()
.position(|&x| x == c as u8)
.map(|i| i as u8)
})
.collect();
let mut bits = Vec::new();
for chunk in values.chunks(2) {
if chunk.len() == 2 {
let val = chunk[0] as u16 * 45 + chunk[1] as u16;
for i in (0..11).rev() {
bits.push((val >> i) & 1 == 1);
}
} else {
// 单个字符 → 6 bit
for i in (0..6).rev() {
bits.push((chunk[0] as u16 >> i) & 1 == 1);
}
}
}
bits
}
/// 字节模式编码: 每字节 → 8 bit
pub fn encode_byte(input: &str) -> Vec<bool> {
let mut bits = Vec::new();
for &byte in input.as_bytes() {
for i in (0..8).rev() {
bits.push((byte >> i) & 1 == 1);
}
}
bits
}
/// 汉字模式编码 (Shift JIS → 13 bit)
pub fn encode_kanji(input: &str) -> Vec<bool> {
let mut bits = Vec::new();
for c in input.chars() {
// 尝试转 Shift JIS
if let Some(code) = char_to_shift_jis(c) {
let val: u16 = if code <= 0x9F {
(code - 0x8140) as u16 // 差 = code - 0x8140
} else {
// 对于 code > 0x9F, 需要处理 0xE040 偏移
(code - 0xC140) as u16 // code - 0xC140
};
let sjis_val = if code <= 0x9FFC {
let hi = (code >> 8) as u16;
let lo = (code & 0xFF) as u16;
if hi >= 0x81 && hi <= 0x9F {
(hi - 0x81) * 0xC0 + (lo - 0x40)
} else {
(hi - 0xC1) * 0xC0 + (lo - 0x40)
}
} else {
let hi = (code >> 8) as u16;
let lo = (code & 0xFF) as u16;
if hi >= 0x81 && hi <= 0x9F {
(hi - 0x81) * 0xC0 + (lo - 0x40)
} else {
(hi - 0xC1) * 0xC0 + (lo - 0x40)
}
};
for i in (0..13).rev() {
bits.push((sjis_val >> i) & 1 == 1);
}
} else {
// 回退到字节模式
let utf8_byte = c as u32;
for i in (0..8).rev() {
bits.push((utf8_byte as u8 >> i) & 1 == 1);
}
}
}
bits
}
/// 判断字符是否属于数字模式
pub fn is_numeric(c: char) -> bool {
c.is_ascii_digit()
}
/// 判断字符是否属于字母数字模式
pub fn is_alphanumeric(c: char) -> bool {
ALPHANUMERIC_CHARS.contains(&(c as u8))
}
/// 判断字符是否为 Shift JIS 汉字
pub fn is_kanji(c: char) -> bool {
// 简易检测:CJK 统一汉字范围
matches!(c,
'\u{4E00}'..='\u{9FFF}' | // CJK 统一汉字
'\u{3400}'..='\u{4DBF}' | // CJK 扩展 A
'\u{3000}'..='\u{303F}' // CJK 标点
)
}
/// 字符转 Shift JIS 码
fn char_to_shift_jis(c: char) -> Option<u16> {
// 仅处理常用汉字
let code = c as u32;
if code >= 0x4E00 && code <= 0x9FFF {
// 简化映射:用 Unicode 码位的简单偏移
// 真正的转换需要查表,这里先做近似
let base = code - 0x4E00;
let hi = 0x81 + (base / 0xC0) as u32;
let lo = 0x40 + (base % 0xC0) as u32;
Some(((hi << 8) | lo) as u16)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_numeric_encode() {
let bits = encode_numeric("123");
// 3 位数字 = 10 bit, 值 123, 二进制 0001111011
assert_eq!(bits.len(), 10);
assert_eq!(bits_to_u16(&bits), 123);
}
#[test]
fn test_numeric_single_digit() {
let bits = encode_numeric("5");
assert_eq!(bits.len(), 4);
assert_eq!(bits_to_u16(&bits), 5);
}
#[test]
fn test_numeric_two_digits() {
let bits = encode_numeric("45");
assert_eq!(bits.len(), 7);
}
#[test]
fn test_alphanumeric_encode() {
let bits = encode_alphanumeric("AB");
// A=10, B=11, val=10*45+11=461 → 11 bit
assert_eq!(bits.len(), 11);
assert_eq!(bits_to_u16(&bits), 461);
}
#[test]
fn test_byte_encode() {
let bits = encode_byte("Hi");
assert_eq!(bits.len(), 16);
assert_eq!(bits[..8].iter().filter(|&&b| b).count(), 3); // 'H' = 72
}
#[test]
fn test_mode_indicator_values() {
assert_eq!(Mode::Numeric.indicator(), 0b0001);
assert_eq!(Mode::Byte.indicator(), 0b0100);
assert_eq!(Mode::Kanji.indicator(), 0b1000);
}
fn bits_to_u16(bits: &[bool]) -> u16 {
bits.iter().fold(0, |acc, &b| (acc << 1) | (b as u16))
}
}
```
注意:Kanji 模式的 Shift JIS 转换是简化实现。真正的转换需要完整的 Unicode→Shift JIS 映射表。对于实际使用,可以在字节模式下处理中文(UTF-8 字节编码),这比 Kanji 模式更实用。
- [ ] **Step 2: 运行测试**
```bash
cd D:\Code\doing_exercises\programs\QRGen && cargo test -p qr-core encoder::mode
```
- [ ] **Step 3: 提交**
```bash
git add . && git commit -m "feat: 四种编码模式(数字/字母/字节/汉字)"
```
---
### Task 6: 数据分段 + 比特流
**Files:**
- Create: `QRGen/core/src/encoder/segment.rs`
- Create: `QRGen/core/src/encoder/bitstream.rs`
- [ ] **Step 1: 实现数据分段**
```rust
// segment.rs
use crate::encoder::mode::{Mode, is_numeric, is_alphanumeric, is_kanji};
/// 数据段:一段连续使用同一种编码模式的数据
#[derive(Debug, Clone)]
pub struct Segment {
pub mode: Mode,
pub char_count: u16,
pub data: String,
}
/// 分析字符串,生成最优分段
pub fn segment_text(text: &str) -> Vec<Segment> {
if text.is_empty() {
return vec![];
}
let chars: Vec<char> = text.chars().collect();
let mut segments = Vec::new();
let mut i = 0;
while i < chars.len() {
// 取当前位置开始的连续同类字符
let range = find_best_run(&chars, i);
let chunk: String = chars[i..range].iter().collect();
let mode = char_mode(chars[i]);
segments.push(Segment {
mode,
char_count: (range - i) as u16,
data: chunk,
});
i = range;
}
segments
}
/// 找到从 pos 开始的最长同模式字符序列
fn find_best_run(chars: &[char], pos: usize) -> usize {
if pos >= chars.len() {
return pos;
}
let current_mode = char_mode(chars[pos]);
let mut end = pos;
for (i, &c) in chars[pos..].iter().enumerate() {
if char_mode(c) != current_mode {
break;
}
end = pos + i + 1;
}
end
}
/// 判断单个字符的最佳编码模式
fn char_mode(c: char) -> Mode {
if is_numeric(c) {
Mode::Numeric
} else if is_alphanumeric(c) {
Mode::Alphanumeric
} else if is_kanji(c) {
Mode::Kanji
} else {
Mode::Byte
}
}
/// 计算段的比特长度
pub fn segment_bit_length(seg: &Segment, version: u8) -> u16 {
let mode_bits = 4u16; // 模式指示符
let count_bits = seg.mode.count_bits(version) as u16;
let data_bits = match seg.mode {
Mode::Numeric => {
let groups_of_3 = seg.char_count / 3;
let remainder = seg.char_count % 3;
groups_of_3 * 10 + if remainder == 2 { 7 } else if remainder == 1 { 4 } else { 0 }
}
Mode::Alphanumeric => {
let groups_of_2 = seg.char_count / 2;
groups_of_2 * 11 + if seg.char_count % 2 == 1 { 6 } else { 0 }
}
Mode::Byte => seg.char_count * 8,
Mode::Kanji => seg.char_count * 13,
};
mode_bits + count_bits + data_bits
}
```
- [ ] **Step 2: 实现比特流拼接**
```rust
// bitstream.rs
use crate::encoder::mode::{Mode, encode_numeric, encode_alphanumeric, encode_byte, encode_kanji};
use crate::encoder::segment::{Segment, segment_text, segment_bit_length};
use crate::version::{Version, EcLevel, get_data_capacity, pick_version};
use crate::VersionMode;
/// 构建最终的码字序列
pub fn build_codewords(text: &str, version: Version, level: EcLevel) -> Vec<u8> {
let segments = segment_text(text);
let mut bits: Vec<bool> = Vec::new();
// 1. 各段编码
for seg in &segments {
// 模式指示符 4 bit
for i in (0..4).rev() {
bits.push((seg.mode.indicator() >> i) & 1 == 1);
}
// 字符计数
let count_bits = seg.mode.count_bits(version.0);
for i in (0..count_bits).rev() {
bits.push((seg.char_count >> i) & 1 == 1);
}
// 数据
let data_bits = match seg.mode {
Mode::Numeric => encode_numeric(&seg.data),
Mode::Alphanumeric => encode_alphanumeric(&seg.data),
Mode::Byte => encode_byte(&seg.data),
Mode::Kanji => encode_kanji(&seg.data),
};
bits.extend(data_bits);
}
// 2. 终止符(最多 4 bit 0
let total_capacity = get_data_capacity(version, level) as usize * 8;
let terminator_len = (4usize).min(total_capacity.saturating_sub(bits.len()));
bits.extend(std::iter::repeat(false).take(terminator_len));
// 3. 补零到 8-bit 边界
while bits.len() % 8 != 0 {
bits.push(false);
}
// 4. 填充码字 0xEC/0x11 交替
let mut pad_byte = 0xECu8;
while bits.len() < total_capacity {
for i in (0..8).rev() {
bits.push((pad_byte >> i) & 1 == 1);
}
pad_byte ^= 0xEC ^ 0x11; // 交替
}
// 5. 比特 → 字节
bits_to_bytes(&bits)
}
fn bits_to_bytes(bits: &[bool]) -> Vec<u8> {
bits.chunks(8)
.map(|chunk| chunk.iter().fold(0u8, |acc, &b| (acc << 1) | (b as u8)))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_codewords_simple() {
let data = build_codewords("123", Version(1), EcLevel::L);
// Version 1 L: 19 数据码字
assert_eq!(data.len(), 19);
}
#[test]
fn test_build_codewords_hello() {
let data = build_codewords("HELLO", Version(1), EcLevel::M);
assert_eq!(data.len(), 16);
}
#[test]
fn test_terminator_and_padding() {
// 短数据应该有填充
let data = build_codewords("A", Version(1), EcLevel::L);
assert_eq!(data.len(), 19);
}
}
```
- [ ] **Step 3: 运行测试**
```bash
cd D:\Code\doing_exercises\programs\QRGen && cargo test -p qr-core encoder::
```
- [ ] **Step 4: 提交**
```bash
git add . && git commit -m "feat: 字符串分段 + 比特流编码"
```
---
### Task 7: 模块矩阵 + 功能图案
**Files:**
- Create: `QRGen/core/src/matrix/grid.rs`
- Create: `QRGen/core/src/matrix/patterns.rs`
- [ ] **Step 1: 定义矩阵网格**
```rust
// grid.rs
/// QR 码模块矩阵
#[derive(Clone)]
pub struct Matrix {
pub size: u8,
pub modules: Vec<Vec<bool>>, // true=暗(黑), false=亮(白)
}
impl Matrix {
pub fn new(size: u8) -> Self {
let modules = vec![vec![false; size as usize]; size as usize];
Matrix { size, modules }
}
pub fn get(&self, x: u8, y: u8) -> bool {
self.modules[y as usize][x as usize]
}
pub fn set(&mut self, x: u8, y: u8, value: bool) {
self.modules[y as usize][x as usize] = value;
}
pub fn is_valid(&self, x: i16, y: i16) -> bool {
x >= 0 && y >= 0 && (x as u8) < self.size && (y as u8) < self.size
}
}
```
- [ ] **Step 2: 绘制功能图案**
```rust
// patterns.rs
use crate::matrix::grid::Matrix;
/// 放置定位图案(3 个角上的大回字形)
pub fn place_finder_patterns(matrix: &mut Matrix) {
let positions = [
(0, 0), // 左上
((matrix.size - 7) as usize, 0), // 右上
(0, (matrix.size - 7) as usize), // 左下
];
for &(x, y) in &positions {
for dy in 0..7 {
for dx in 0..7 {
let is_dark = match (dx, dy) {
// 外边框 (0 和 6)
(0, _) | (6, _) | (_, 0) | (_, 6) => true,
// 中间十字 (2-4, 2-4)
(2..=4, 2..=4) => true,
// 其余为亮
_ => false,
};
matrix.set((x + dx) as u8, (y + dy) as u8, is_dark);
}
}
}
}
/// 放置时序图案(行 6 和列 6 的交替线)
pub fn place_timing_patterns(matrix: &mut Matrix) {
// 水平时序线 (y = 6)
for x in 8..(matrix.size - 8) as usize {
matrix.set(x as u8, 6, x % 2 == 0);
}
// 垂直时序线 (x = 6)
for y in 8..(matrix.size - 8) as usize {
matrix.set(6, y as u8, y % 2 == 0);
}
}
/// 放置对齐图案(回字形 5×5)
pub fn place_alignment_patterns(matrix: &mut Matrix, positions: &[u8]) {
for &cy in positions {
for &cx in positions {
// 跳过与定位图案重叠的位置
if is_near_finder(cx, cy, matrix.size) {
continue;
}
place_single_alignment(matrix, cx, cy);
}
}
}
fn place_single_alignment(matrix: &mut Matrix, cx: u8, cy: u8) {
let x0 = (cx - 2) as usize;
let y0 = (cy - 2) as usize;
for dy in 0..5 {
for dx in 0..5 {
let is_dark = match (dx, dy) {
(0, _) | (4, _) | (_, 0) | (_, 4) => true, // 外边框
(2, 2) => true, // 中心
_ => false,
};
matrix.set((x0 + dx) as u8, (y0 + dy) as u8, is_dark);
}
}
}
/// 检查坐标是否与定位图案重叠
fn is_near_finder(x: u8, y: u8, size: u8) -> bool {
let s = size as i16;
let x = x as i16;
let y = y as i16;
// 左上角定位图案区域 (0..7, 0..7)
if x - 2 < 7 && y - 2 < 7 { return true; }
// 右上角 (size-7..size, 0..7)
if x + 2 >= s - 7 && y - 2 < 7 { return true; }
// 左下角 (0..7, size-7..size)
if x - 2 < 7 && y + 2 >= s - 7 { return true; }
false
}
/// 放置暗模块 (版本 ≥ 2 时,在 (8, 4*ver+9) 处)
pub fn place_dark_module(matrix: &mut Matrix, version: u8) {
// 暗模块总是位于 (8, 4*version + 9)
// 注意:按标准,y 从底部算起,此处坐标体系可能需翻转
let size = matrix.size;
matrix.set(8, size - 8, true);
}
/// 预留格式信息区域(15 bit),先标记为 false(会被掩码后的最终格式信息覆盖)
pub fn reserve_format_areas(matrix: &mut Matrix) {
let size = matrix.size;
// 定位图案旁的格式信息条
for i in 0..9 {
// 左上水平 (0..8, 8) 跳过时序线交叉点
if i != 6 { matrix.set(i, 8, false); }
// 左上垂直 (8, 0..8)
if i != 6 { matrix.set(8, i, false); }
// 右上垂直 (8, size-8..size-1)
if i != 6 && i + size - 8 < size { matrix.set(8, size - 1 - i, false); }
// 左下水平 (size-8..size-1, 8)
if i != 6 && i + size - 8 < size { matrix.set(size - 1 - i, 8, false); }
}
// 暗模块位置
matrix.set(8, size - 8, true);
}
/// 预留版本信息区域(版本 ≥ 7,18 bit)
pub fn reserve_version_areas(matrix: &mut Matrix) {
let size = matrix.size;
// 版本信息在定位图案旁边,共 3×6 的两块
for i in 0..6 {
for j in 0..3 {
// 右上角旁边
matrix.set(size - 11 + j, i, false);
// 左下角旁边
matrix.set(i, size - 11 + j, false);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_finder_patterns_v1() {
let mut m = Matrix::new(21);
place_finder_patterns(&mut m);
// 三个角的外边框应该有黑色模块
assert!(m.get(0, 0));
assert!(m.get(20, 0));
assert!(m.get(0, 20));
// 定位图案中心 (3,3) 是黑的
assert!(m.get(3, 3));
}
#[test]
fn test_timing_patterns_alternate() {
let mut m = Matrix::new(21);
place_timing_patterns(&mut m);
assert!(m.get(8, 6)); // 偶数 = 暗
assert!(!m.get(9, 6)); // 奇数 = 亮
}
}
```
- [ ] **Step 3: 运行测试**
```bash
cd D:\Code\doing_exercises\programs\QRGen && cargo test -p qr-core matrix::patterns
```
- [ ] **Step 4: 提交**
```bash
git add . && git commit -m "feat: QR 矩阵 + 功能图案绘制"
```
---
### Task 8: 数据排列 + 掩码
**Files:**
- Create: `QRGen/core/src/matrix/placement.rs`
- Create: `QRGen/core/src/matrix/mask.rs`
- [ ] **Step 1: 蛇形数据排列**
```rust
// placement.rs
use crate::matrix::grid::Matrix;
/// 将码字比特按蛇形路径放入矩阵数据区域
pub fn place_data(matrix: &mut Matrix, codewords: &[u8]) {
let size = matrix.size as usize;
// 比特流
let mut bits: Vec<bool> = Vec::new();
for &cw in codewords {
for i in (0..8).rev() {
bits.push((cw >> i) & 1 == 1);
}
}
let mut bit_idx = 0;
let mut col = (size - 1) as i16; // 从右下角开始
let mut going_up = true; // 向上扫描
while col >= 0 {
// 跳过垂直时序线 (col=6)
let actual_col = if col == 6 { 5 } else { col };
if actual_col < 0 { break; }
if going_up {
for row in (0..size as i16).rev() {
if bit_idx >= bits.len() { break; }
if can_place(matrix, actual_col as u8, row as u8) {
matrix.set(actual_col as u8, row as u8, bits[bit_idx]);
bit_idx += 1;
}
if bit_idx >= bits.len() { break; }
if can_place(matrix, (actual_col - 1) as u8, row as u8) {
matrix.set((actual_col - 1) as u8, row as u8, bits[bit_idx]);
bit_idx += 1;
}
}
} else {
for row in 0..size as i16 {
if bit_idx >= bits.len() { break; }
if can_place(matrix, actual_col as u8, row as u8) {
matrix.set(actual_col as u8, row as u8, bits[bit_idx]);
bit_idx += 1;
}
if bit_idx >= bits.len() { break; }
if can_place(matrix, (actual_col - 1) as u8, row as u8) {
matrix.set((actual_col - 1) as u8, row as u8, bits[bit_idx]);
bit_idx += 1;
}
}
}
col -= 2;
going_up = !going_up;
}
}
/// 检查 (x, y) 是否为功能图案区域,功能图案区域不能放数据
fn can_place(matrix: &Matrix, x: u8, y: u8) -> bool {
// 边界检查
if x >= matrix.size || y >= matrix.size {
return false;
}
// 如果已经设置了值(功能图案),跳过
// 我们用一个辅助标记来追踪:数据区域初始化为一种状态,功能图案是另一种
// 简化做法:总是返回 true,允许覆盖,掩码步骤会处理
true
}
```
注意:`can_place` 需要知道哪些位置已经被功能图案占用。更好的做法是在 Matrix 中维护一个 `is_reserved` 数组。
- [ ] **Step 2: 改进 Matrix 支持保留区域跟踪**
`grid.rs` 中增加:
```rust
impl Matrix {
// ...
pub fn new(size: u8) -> Self {
let modules = vec![vec![false; size as usize]; size as usize];
let reserved = vec![vec![false; size as usize]; size as usize];
Matrix { size, modules, reserved }
}
/// 标记为功能图案区域
pub fn reserve(&mut self, x: u8, y: u8) {
if x < self.size && y < self.size {
self.reserved[y as usize][x as usize] = true;
}
}
/// 是否被功能图案占用
pub fn is_reserved(&self, x: u8, y: u8) -> bool {
x < self.size && y < self.size && self.reserved[y as usize][x as usize]
}
}
```
同时更新 `patterns.rs` 中所有 `matrix.set()` 调用为先 `matrix.set()` 然后 `matrix.reserve()`
- [ ] **Step 3: 实现掩码 + 评分**
```rust
// mask.rs
use crate::matrix::grid::Matrix;
/// 掩码函数类型: f(x, y) = true 时翻转模块
type MaskFn = fn(u8, u8) -> bool;
/// 8 种标准掩码
pub const MASK_FNS: [MaskFn; 8] = [
|x, y| (x + y) % 2 == 0, // 000
|_, y| y % 2 == 0, // 001
|x, _| x % 3 == 0, // 010
|x, y| (x + y) % 3 == 0, // 011
|x, y| ((y / 2) + (x / 3)) % 2 == 0, // 100
|x, y| (x * y) % 2 + (x * y) % 3 == 0, // 101
|x, y| ((x * y) % 2 + (x * y) % 3) % 2 == 0, // 110
|x, y| ((x + y) % 2 + (x * y) % 3) % 2 == 0, // 111
];
/// 应用掩码到矩阵(仅数据区域,跳过功能图案)
pub fn apply_mask(matrix: &Matrix, mask_fn: MaskFn) -> Matrix {
let mut result = matrix.clone();
for y in 0..matrix.size {
for x in 0..matrix.size {
if !matrix.is_reserved(x, y) && mask_fn(x, y) {
let current = result.get(x, y);
result.set(x, y, !current);
}
}
}
result
}
/// 掩码惩罚评分(越低越好)
pub fn score(matrix: &Matrix) -> u32 {
let mut penalty = 0u32;
// 规则 1: 水平/垂直连续 5+ 同色
penalty += score_rule1(matrix);
// 规则 2: 同色 2×2 方块
penalty += score_rule2(matrix);
// 规则 3: 1011101 模式
penalty += score_rule3(matrix);
// 规则 4: 暗模块占比偏离 50%
penalty += score_rule4(matrix);
penalty
}
fn score_rule1(matrix: &Matrix) -> u32 {
let mut penalty = 0u32;
let n = matrix.size as usize;
// 水平扫描
for y in 0..n {
let mut run = 0u32;
let mut prev = matrix.get(0, y as u8);
for x in 1..n {
let cur = matrix.get(x as u8, y as u8);
if cur == prev {
run += 1;
} else {
if run >= 5 { penalty += 3 + run - 5; }
run = 1;
prev = cur;
}
}
if run >= 5 { penalty += 3 + run - 5; }
}
// 垂直扫描
for x in 0..n {
let mut run = 0u32;
let mut prev = matrix.get(x as u8, 0);
for y in 1..n {
let cur = matrix.get(x as u8, y as u8);
if cur == prev {
run += 1;
} else {
if run >= 5 { penalty += 3 + run - 5; }
run = 1;
prev = cur;
}
}
if run >= 5 { penalty += 3 + run - 5; }
}
penalty
}
fn score_rule2(matrix: &Matrix) -> u32 {
let mut count = 0u32;
let n = matrix.size as u8;
for y in 0..n-1 {
for x in 0..n-1 {
let v = matrix.get(x, y);
if matrix.get(x+1, y) == v && matrix.get(x, y+1) == v && matrix.get(x+1, y+1) == v {
count += 1;
}
}
}
count * 3
}
fn score_rule3(matrix: &Matrix) -> u32 {
let pattern: [bool; 11] = [
true, false, true, true, true, false, true, // dark-light-dark-dark-dark-light-dark
false, false, false, false, // 前面是 1 后面是 0
];
let penalty_per = 40u32;
let mut penalty = 0u32;
let n = matrix.size as usize;
// 水平
for y in 0..n {
for x in 0..n-10 {
let matches = (0..11).all(|i| {
matrix.get((x+i) as u8, y as u8) == pattern[i]
});
if matches { penalty += penalty_per; }
}
}
// 垂直
for x in 0..n {
for y in 0..n-10 {
let matches = (0..11).all(|i| {
matrix.get(x as u8, (y+i) as u8) == pattern[i]
});
if matches { penalty += penalty_per; }
}
}
penalty
}
fn score_rule4(matrix: &Matrix) -> u32 {
let total = (matrix.size as u32) * (matrix.size as u32);
let dark: u32 = (0..matrix.size).flat_map(|y|
(0..matrix.size).map(move |x| matrix.get(x, y) as u32)
).sum();
let pct = dark * 100 / total;
let deviation = ((pct as i32 - 50).abs() as u32) / 5;
deviation * 10
}
/// 评估所有掩码,返回最佳掩码编号 (0-7) 和对应矩阵
pub fn best_mask(matrix: &Matrix) -> (u8, Matrix) {
let mut best_idx = 0u8;
let mut best_score = u32::MAX;
let mut best_matrix = matrix.clone();
for i in 0..8 {
let masked = apply_mask(matrix, MASK_FNS[i]);
let s = score(&masked);
if s < best_score {
best_score = s;
best_idx = i as u8;
best_matrix = masked;
}
}
(best_idx, best_matrix)
}
```
- [ ] **Step 4: 运行测试**
```bash
cd D:\Code\doing_exercises\programs\QRGen && cargo test -p qr-core matrix::
```
- [ ] **Step 5: 提交**
```bash
git add . && git commit -m "feat: 蛇形数据排列 + 8 种掩码 + 评分"
```
---
### Task 9: 格式信息 + 版本信息编码
**Files:**
- Modify: `QRGen/core/src/matrix/patterns.rs` (追加)
- [ ] **Step 1: 添加格式信息编码**
`patterns.rs` 末尾追加:
```rust
/// 格式信息 = 2 bit EC 级别 + 3 bit 掩码编号 → BCH(15,5) 编码 → 15 bit
pub fn encode_format_info(level: u8, mask: u8) -> u16 {
let data = ((level & 0x03) << 3) | (mask & 0x07);
let mut encoded = data as u16;
// BCH(15,5) 编码:生成多项式 x¹⁰ + x⁸ + x⁵ + x⁴ + x² + x + 1 = 0x537
let gen: u16 = 0x537;
let mut val = encoded << 10;
for i in (4..=14).rev() {
if (val >> i) & 1 == 1 {
val ^= gen << (i - 10);
}
}
encoded = ((data as u16) << 10) | (val & 0x3FF);
// XOR 掩码 0x5412
encoded ^ 0x5412
}
/// 将格式信息写入矩阵
pub fn place_format_info(matrix: &mut Matrix, format: u16) {
let size = matrix.size;
// 坐标序列(15 个位置,x,y 坐标对)
let coords = [
(0, 8), (1, 8), (2, 8), (3, 8), (4, 8), (5, 8), (7, 8), (8, 8),
(8, 7), (8, 5), (8, 4), (8, 3), (8, 2), (8, 1), (8, 0),
];
let coords2 = [
(8, size - 1), (8, size - 2), (8, size - 3), (8, size - 4),
(8, size - 5), (8, size - 6), (8, size - 7),
(size - 8, 8), (size - 7, 8), (size - 6, 8), (size - 5, 8),
(size - 4, 8), (size - 3, 8), (size - 2, 8), (size - 1, 8),
];
for i in 0..15 {
let bit = (format >> (14 - i)) & 1 == 1;
let (x, y) = coords[i];
matrix.set(x, y, bit);
let (x2, y2) = coords2[i];
matrix.set(x2, y2, bit);
}
}
/// 版本信息编码 (版本 ≥ 7): BCH(18,6)
pub fn encode_version_info(version: u8) -> u32 {
let data = version as u32;
let gen: u32 = 0x1F25; // x¹² + x¹¹ + x¹⁰ + x⁹ + x⁸ + x⁵ + x² + 1
let mut val = data << 12;
for i in (5..=17).rev() {
if (val >> i) & 1 == 1 {
val ^= gen << (i - 12);
}
}
(data << 12) | (val & 0xFFF)
}
/// 将版本信息写入矩阵
pub fn place_version_info(matrix: &mut Matrix, version_info: u32) {
let size = matrix.size;
// 在定位图案旁放置 6×3 的版本信息块
for i in 0..6 {
for j in 0..3 {
let bit = (version_info >> (17 - (i * 3 + j))) & 1 == 1;
// 右上角旁边
matrix.set(size - 11 + j, i, bit);
// 左下角旁边
matrix.set(i, size - 11 + j, bit);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_info_known() {
// M 级 (00) + mask 3 (011) → 预期值
let info = encode_format_info(0b00, 0b011);
// 验证 XOR 掩码后为非零值
assert!(info > 0);
assert_eq!(info & 0x7FFF, info); // 15 bit 内
}
#[test]
fn test_version_info_known() {
let info = encode_version_info(7);
assert_eq!((info >> 12) & 0x3F, 7); // 前 6 bit 是版本号
}
}
```
- [ ] **Step 2: 运行测试**
```bash
cd D:\Code\doing_exercises\programs\QRGen && cargo test -p qr-core matrix::
```
- [ ] **Step 3: 提交**
```bash
git add . && git commit -m "feat: 格式信息 BCH 编码 + 版本信息编码"
```
---
### Task 10: 顶层 API + 渲染器
**Files:**
- Create: `QRGen/core/src/qr.rs`
- Create: `QRGen/core/src/render/png.rs`
- Create: `QRGen/core/src/render/svg.rs`
- Create: `QRGen/core/src/render/ascii.rs`
- [ ] **Step 1: 实现顶层 API**
```rust
// qr.rs
use crate::version::{Version, EcLevel, pick_version, get_data_capacity};
use crate::encoder::bitstream::build_codewords;
use crate::encoder::segment::{segment_text, segment_bit_length};
use crate::ecc::reed_solomon;
use crate::matrix::grid::Matrix;
use crate::matrix::patterns::{
place_finder_patterns, place_timing_patterns, place_alignment_patterns,
place_dark_module, reserve_format_areas, reserve_version_areas,
encode_format_info, encode_version_info, place_format_info, place_version_info,
};
use crate::matrix::placement::place_data;
use crate::matrix::mask::{best_mask, apply_mask, MASK_FNS};
#[derive(Debug, Clone)]
pub enum VersionMode {
Auto,
Fixed(u8),
}
#[derive(Debug, Clone)]
pub enum ModeHint {
Auto,
Numeric,
Alphanumeric,
Byte,
Kanji,
}
#[derive(Debug, Clone)]
pub struct QrConfig {
pub level: EcLevel,
pub version: VersionMode,
pub margin: u8, // 白边模块数
}
impl Default for QrConfig {
fn default() -> Self {
QrConfig {
level: EcLevel::M,
version: VersionMode::Auto,
margin: 4,
}
}
}
pub struct QrCode {
pub version: Version,
pub level: EcLevel,
pub mask: u8,
matrix: Matrix,
margin: u8,
}
impl QrCode {
/// 编码字符串生成 QR 码
pub fn encode(text: &str, config: QrConfig) -> Result<Self, String> {
// 1. 分段
let segments = segment_text(text);
if segments.is_empty() {
return Err("输入为空".into());
}
// 2. 确定版本
let version = match config.version {
VersionMode::Fixed(v) => Version::new(v).ok_or("无效版本号")?,
VersionMode::Auto => {
// 计算总比特数(近似),逐步尝试
let mut selected = None;
for v in 1..=40 {
let ver = Version(v);
let total_bits: u16 = segments.iter()
.map(|s| segment_bit_length(s, v))
.sum();
let cap = get_data_capacity(ver, config.level) as u32 * 8;
if cap >= total_bits as u32 {
selected = Some(ver);
break;
}
}
selected.ok_or("数据过长,超出 QR 码最大容量")?
}
};
// 3. 构建数据码字
let data = build_codewords(text, version, config.level);
// 4. 纠错编码
let ec_info = version.ec_info(config.level);
let mut blocks: Vec<Vec<u8>> = Vec::new();
let mut pos = 0;
for binfo in &ec_info.blocks {
for _ in 0..binfo.count {
let end = pos + binfo.data_codewords as usize;
blocks.push(data[pos..end].to_vec());
pos = end;
}
}
let final_data = reed_solomon::interleave(&blocks, ec_info.ec_per_block);
// 5. 构建矩阵
let mut matrix = Matrix::new(version.size());
place_finder_patterns(&mut matrix);
// 标记保留区域
mark_reserved(&mut matrix, version);
place_timing_patterns(&mut matrix);
place_alignment_patterns(&mut matrix, version.alignment_positions());
if version.0 >= 2 {
place_dark_module(&mut matrix, version.0);
}
reserve_format_areas(&mut matrix);
if version.0 >= 7 {
reserve_version_areas(&mut matrix);
}
// 6. 放置数据
place_data(&mut matrix, &final_data);
// 7. 掩码评分
let (best_idx, best_matrix) = best_mask(&matrix);
// 8. 写入格式信息
let format = encode_format_info(config.level.indicator_bits(), best_idx);
place_format_info(&mut best_matrix.clone(), format); // 需要覆盖
Ok(QrCode {
version,
level: config.level,
mask: best_idx,
matrix: best_matrix,
margin: config.margin,
})
}
pub fn modules(&self) -> &[Vec<bool>] {
&self.matrix.modules
}
pub fn size(&self) -> u8 {
self.matrix.size
}
pub fn to_svg(&self) -> String {
crate::render::svg::render_svg(self)
}
pub fn to_ascii(&self, invert: bool) -> String {
crate::render::ascii::render_ascii(self, invert)
}
pub fn to_png_bytes(&self, module_size: u8) -> Vec<u8> {
crate::render::png::render_png(self, module_size)
}
}
fn mark_reserved(matrix: &mut Matrix, version: Version) {
let size = matrix.size;
// 定位图案
for &(fx, fy) in &[(0u8, 0u8), (size - 7, 0), (0, size - 7)] {
for dy in 0..7u8 {
for dx in 0..7u8 {
matrix.reserve(fx + dx, fy + dy);
}
}
}
// 时序线
for x in 8..size - 8 {
matrix.reserve(x, 6);
matrix.reserve(6, x);
}
// 对齐图案
// ... 省略,根据实际需要调用
// 格式信息区域
// 版本信息区域
}
```
注意:`mark_reserved` 需要完整实现,在具体编码时补齐所有保留区域标记。
- [ ] **Step 2: 实现渲染器**
```rust
// render/png.rs
use crate::qr::QrCode;
use image::{ImageBuffer, Luma};
pub fn render_png(qr: &QrCode, module_size: u8) -> Vec<u8> {
let matrix_size = qr.size() as u32;
let margin = qr.margin as u32;
let total_size = matrix_size + 2 * margin;
let img_size = total_size * module_size as u32;
let mut img = ImageBuffer::new(img_size, img_size);
for y in 0..total_size {
for x in 0..total_size {
let module_x = x.saturating_sub(margin);
let module_y = y.saturating_sub(margin);
let is_dark = if module_x < matrix_size && module_y < matrix_size {
qr.modules()[module_y as usize][module_x as usize]
} else {
false // 白边
};
let px_val = if is_dark { 0u8 } else { 255u8 };
for dy in 0..module_size as u32 {
for dx in 0..module_size as u32 {
img.put_pixel(
x * module_size as u32 + dx,
y * module_size as u32 + dy,
Luma([px_val]),
);
}
}
}
}
let mut buf = Vec::new();
img.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png)
.expect("PNG 编码失败");
buf
}
```
```rust
// render/svg.rs
use crate::qr::QrCode;
pub fn render_svg(qr: &QrCode) -> String {
let matrix_size = qr.size() as u32;
let margin = qr.margin as u32;
let total = matrix_size + 2 * margin;
let mut svg = String::new();
svg.push_str(&format!(
r#"<svg xmlns="http://www.w3.org/2000/svg" width="{}" height="{}" viewBox="0 0 {} {}">"#,
total, total, total, total
));
svg.push_str(&format!(
r#"<rect width="{}" height="{}" fill="white"/>"#,
total, total
));
for y in 0..matrix_size {
for x in 0..matrix_size {
if qr.modules()[y as usize][x as usize] {
svg.push_str(&format!(
r#"<rect x="{}" y="{}" width="1" height="1" fill="black"/>"#,
x + margin, y + margin
));
}
}
}
svg.push_str("</svg>");
svg
}
```
```rust
// render/ascii.rs
use crate::qr::QrCode;
pub fn render_ascii(qr: &QrCode, invert: bool) -> String {
let size = qr.size() as usize;
let margin = qr.margin as usize;
let total = size + 2 * margin;
let dark_char = if invert { " " } else { "██" };
let light_char = if invert { "██" } else { " " };
let mut result = String::new();
for y in 0..total {
for x in 0..total {
let mx = x.saturating_sub(margin);
let my = y.saturating_sub(margin);
let is_dark = if mx < size && my < size {
qr.modules()[my][mx]
} else {
false
};
result.push_str(if is_dark { dark_char } else { light_char });
}
result.push('\n');
}
result
}
```
- [ ] **Step 3: 提交**
```bash
git add . && git commit -m "feat: 顶层 API + PNG/SVG/ASCII 渲染器"
```
---
### Task 11: CLI 工具
**Files:**
- Modify: `QRGen/cli/src/main.rs`
- [ ] **Step 1: 实现 CLI**
```rust
// cli/src/main.rs
use clap::Parser;
use qr_core::qr::{QrCode, QrConfig, VersionMode};
use qr_core::version::EcLevel;
use std::path::Path;
#[derive(Parser)]
#[command(name = "qrgen", about = "QR 码生成器")]
struct Args {
/// 要编码的内容
content: String,
/// 输出文件 (.png 或 .svg),不指定则输出终端 ASCII
#[arg(short = 'o', long)]
output: Option<String>,
/// 纠错级别 [L/M/Q/H]
#[arg(short = 'l', long, default_value = "M")]
level: String,
/// 手动指定版本 (1-40),不指定则自动
#[arg(short = 'v', long)]
version: Option<u8>,
/// 模块像素大小(仅 PNG
#[arg(short = 's', long, default_value = "4")]
size: u8,
/// 白边模块数
#[arg(short = 'm', long, default_value = "4")]
margin: u8,
/// 反色
#[arg(long)]
invert: bool,
}
fn main() -> anyhow::Result<()> {
let args = Args::parse();
let level = match args.level.to_uppercase().as_str() {
"L" => EcLevel::L,
"M" => EcLevel::M,
"Q" => EcLevel::Q,
"H" => EcLevel::H,
_ => anyhow::bail!("无效纠错级别: {}。支持 L/M/Q/H", args.level),
};
let version = match args.version {
Some(v) => VersionMode::Fixed(v),
None => VersionMode::Auto,
};
let config = QrConfig {
level,
version,
margin: args.margin,
};
let qr = QrCode::encode(&args.content, config)
.map_err(|e| anyhow::anyhow!("编码失败: {}", e))?;
match args.output {
Some(path) => {
let ext = Path::new(&path)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_lowercase();
match ext.as_str() {
"png" => {
let bytes = qr.to_png_bytes(args.size);
std::fs::write(&path, bytes)?;
println!("已生成: {}", path);
}
"svg" => {
let svg = qr.to_svg();
std::fs::write(&path, svg)?;
println!("已生成: {}", path);
}
_ => anyhow::bail!("不支持的文件格式: .{}。支持 .png / .svg", ext),
}
}
None => {
// 终端 ASCII 输出
println!("{}", qr.to_ascii(args.invert));
}
}
Ok(())
}
```
- [ ] **Step 2: 构建并测试 CLI**
```bash
cd D:\Code\doing_exercises\programs\QRGen
cargo build
cargo run -p qrgen -- "Hello World" # ASCII 输出
cargo run -p qrgen -- "Hello" -o test.png -s 10 # PNG 输出
cargo run -p qrgen -- "Hello" -o test.svg # SVG 输出
```
- [ ] **Step 3: 提交**
```bash
git add . && git commit -m "feat: CLI 工具 (clap + anyhow)"
```
---
### Task 12: 集成测试 + 端到端验证
**Files:**
- Modify: `QRGen/core/Cargo.toml` (添加 dev-dependencies)
- Create: `QRGen/core/tests/integration_test.rs`
- [ ] **Step 1: 添加集成测试**
`core/Cargo.toml` 中添加:
```toml
[dev-dependencies]
```
```rust
// core/tests/integration_test.rs
use qr_core::qr::{QrCode, QrConfig, VersionMode};
use qr_core::version::EcLevel;
#[test]
fn test_encode_simple_text() {
let config = QrConfig::default();
let qr = QrCode::encode("HELLO WORLD", config).unwrap();
assert_eq!(qr.version.0, 1); // 短文本应该是 version 1
assert_eq!(qr.size(), 21);
}
#[test]
fn test_all_levels() {
for level in [EcLevel::L, EcLevel::M, EcLevel::Q, EcLevel::H] {
let config = QrConfig { level, ..Default::default() };
let qr = QrCode::encode("TEST", config).unwrap();
assert!(qr.size() >= 21);
}
}
#[test]
fn test_chinese_text() {
let config = QrConfig::default();
let qr = QrCode::encode("你好世界", config).unwrap();
assert!(qr.size() >= 21);
}
#[test]
fn test_url() {
let config = QrConfig::default();
let qr = QrCode::encode("https://example.com/path?q=1", config).unwrap();
assert!(qr.size() >= 21);
}
#[test]
fn test_numeric_only() {
let mut config = QrConfig::default();
config.version = VersionMode::Fixed(1);
// 纯数字,Version 1 L 级够用
let qr = QrCode::encode("12345678901234567890", config).unwrap();
assert_eq!(qr.version.0, 1);
}
#[test]
fn test_fixed_version() {
let config = QrConfig {
version: VersionMode::Fixed(5),
..Default::default()
};
let qr = QrCode::encode("FIXED VERSION TEST", config).unwrap();
assert_eq!(qr.version.0, 5);
}
#[test]
fn test_empty_input_fails() {
let config = QrConfig::default();
let result = QrCode::encode("", config);
assert!(result.is_err());
}
#[test]
fn test_svg_output_contains_svg_tag() {
let qr = QrCode::encode("TEST", QrConfig::default()).unwrap();
let svg = qr.to_svg();
assert!(svg.contains("<svg"));
assert!(svg.contains("</svg>"));
}
#[test]
fn test_ascii_output_not_empty() {
let qr = QrCode::encode("TEST", QrConfig::default()).unwrap();
let ascii = qr.to_ascii(false);
assert!(!ascii.is_empty());
assert!(ascii.contains('\n'));
}
#[test]
fn test_png_output_has_size() {
let qr = QrCode::encode("TEST", QrConfig::default()).unwrap();
let png = qr.to_png_bytes(4);
assert!(!png.is_empty());
// PNG 文件应以 8 字节签名开头
assert_eq!(&png[..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
}
```
- [ ] **Step 2: 运行全部测试**
```bash
cd D:\Code\doing_exercises\programs\QRGen
cargo test
```
- [ ] **Step 3: 提交**
```bash
git add . && git commit -m "test: 集成测试 + 端到端验证"
```
---
## 实现顺序
```
Task 1 (骨架) ──→ Task 2 (Galois) ──→ Task 4 (RS)
↘ ↗
Task 3 (版本表) ──→ Task 5 (编码) ──→ Task 6 (分段+比特流)
Task 7 (矩阵+图案) ←── Task 8 (排列+掩码) ←── Task 9 (格式/版本信息)
Task 10 (API+渲染) ──→ Task 11 (CLI) ──→ Task 12 (集成测试)
```
每个 Task 完成后运行 `cargo test` 确保不退化。