fix: 格式信息 BCH 编码运算符优先级 — | 低于 ^ 导致 XOR 未覆盖数据位

Bug: ((data << 10) | (val & 0x3FF)) ^ 0x5412 被解析为 (data << 10) | ((val & 0x3FF) ^ 0x5412)
导致格式信息的 XOR mask 未覆盖前 5 个数据位,扫码器读到错误的 EC 级别和掩码编号
This commit is contained in:
2026-06-17 09:26:09 +08:00
parent 11d8cffa57
commit 3f1b9901b5
2 changed files with 180 additions and 2 deletions
+178
View File
@@ -1,6 +1,184 @@
use qr_core::qr::{QrCode, QrConfig, VersionMode};
use qr_core::version::EcLevel;
/// 诊断: 验证格式信息编码 + 解码是否正确
#[test]
fn test_format_info_roundtrip() {
use qr_core::matrix::patterns::{encode_format_info, encode_version_info};
// M 级 (00) + mask 0 (000): data = 00000 = 0
let fmt0 = encode_format_info(0b00, 0);
// L 级 (01) + mask 3 (011): data = 01011 = 11
let fmt1 = encode_format_info(0b01, 3);
// H 级 (10) + mask 7 (111): data = 10111 = 23
let fmt2 = encode_format_info(0b10, 7);
// 不同输入应产生不同输出
assert_ne!(fmt0, fmt1);
assert_ne!(fmt1, fmt2);
assert_ne!(fmt0, fmt2);
// 应该在 15-bit 范围内
assert!(fmt0 < 0x8000);
assert!(fmt1 < 0x8000);
println!("格式信息 M+mask0: 0x{:04X}", fmt0);
println!("格式信息 L+mask3: 0x{:04X}", fmt1);
println!("格式信息 H+mask7: 0x{:04X}", fmt2);
// 版本信息编码
let v7 = encode_version_info(7);
println!("版本信息 v7: 0x{:06X}", v7);
// 前 6 bit 是版本号
assert_eq!((v7 >> 12) & 0x3F, 7);
}
/// 诊断: 打印 QR 码的格式信息比特
#[test]
fn test_dump_format_info() {
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
let m = qr.modules();
println!("\n=== 格式信息 15 bit (bit14 到 bit0) ===");
println!("EC 级别: {:?}, 掩码: {}", qr.level, qr.mask);
// 格式信息位置 (按标准顺序 bit14→bit0)
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 mut fmt_bits = 0u16;
for (i, &(x, y)) in coords.iter().enumerate() {
let bit = if m[y as usize][x as usize] { 1u16 } else { 0u16 };
fmt_bits = (fmt_bits << 1) | bit;
print!("{} ", if m[y as usize][x as usize] { '█' } else { '_' });
}
println!();
println!("读取的格式信息 (原始, 含 XOR mask 0x5412): 0x{:04X}", fmt_bits);
// 去掉 XOR mask
let unmasked = fmt_bits ^ 0x5412;
println!("去 XOR mask 后: 0x{:04X}", unmasked);
let ec_bits = (unmasked >> 13) & 0x03;
let mask_bits = (unmasked >> 10) & 0x07;
let bch = unmasked & 0x3FF;
println!("EC bits: {:02b} 掩码 bits: {:03b} BCH: 0x{:03X}", ec_bits, mask_bits, bch);
// 期望值
let expected = {
use qr_core::matrix::patterns::encode_format_info;
encode_format_info(qr.level.indicator_bits(), qr.mask)
};
println!("期望的格式信息: 0x{:04X}", expected);
println!("匹配: {}", if fmt_bits == expected { "" } else { "❌ 不匹配!" });
assert_eq!(fmt_bits, expected, "格式信息不匹配!");
}
#[test]
fn test_finder_patterns_present() {
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
let size = qr.size() as usize;
let m = qr.modules();
// 三个定位图案: (0,0), (size-7,0), (0,size-7)
let s = size as u8;
let finders: [(u8, u8); 3] = [(0, 0), (s - 7, 0), (0, s - 7)];
for (fx, fy) in finders {
assert!(m[fy as usize][fx as usize], "定位({},{}): 左上角应为暗", fx, fy);
assert!(m[fy as usize][(fx + 6) as usize], "定位({},{}): 右上角应为暗", fx, fy);
assert!(m[(fy + 6) as usize][fx as usize], "定位({},{}): 左下角应为暗", fx, fy);
// 内部 3×3 是暗色
assert!(m[(fy + 2) as usize][(fx + 2) as usize], "定位({},{}): 中心3×3应为暗", fx, fy);
}
}
#[test]
fn test_timing_pattern_alternates() {
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
let m = qr.modules();
let s = qr.size() as usize;
// 行6: 从列8到列s-8-1 应交替
for x in (8..s - 8).step_by(2) {
assert!(m[6][x], "时序 y=6 x={}: 偶数应为暗", x);
if x + 1 < s - 8 {
assert!(!m[6][x + 1], "时序 y=6 x={}: 奇数应为亮", x + 1);
}
}
// 列6: 从行8到行s-8-1 应交替
for y in (8..s - 8).step_by(2) {
assert!(m[y][6], "时序 x=6 y={}: 偶数应为暗", y);
if y + 1 < s - 8 {
assert!(!m[y + 1][6], "时序 x=6 y={}: 奇数应为亮", y + 1);
}
}
}
#[test]
fn test_dark_module_present() {
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
let m = qr.modules();
let s = qr.size() as usize;
// 暗模块总是位于 (8, size-8)
assert!(m[s - 8][8], "暗模块 (8,{}) 缺失!", s - 8);
}
#[test]
fn test_format_info_written() {
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
let m = qr.modules();
// 格式信息在定位图案旁,检查几个位置不是全亮
assert!(m[8][0] || m[8][1] || m[8][2] || !m[8][0],
"格式信息应已写入");
}
#[test]
fn test_svg_valid_structure() {
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
let svg = qr.to_svg();
// SVG 应有正确的结构
assert!(svg.starts_with("<svg"), "SVG 应以 <svg 开头");
assert!(svg.contains("rect"), "SVG 应包含 rect 元素");
assert!(svg.contains("fill=\"black\""), "SVG 暗模块应是黑色");
assert!(svg.ends_with("</svg>\n") || svg.ends_with("</svg>"),
"SVG 应以 </svg> 结尾");
}
#[test]
fn test_quiet_zone_is_white() {
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
let m = qr.modules();
let s = qr.size() as usize;
// 左上角分隔符区域 (7,0..7) 和 (0..7,7) 应为白色
for i in 0..8usize {
assert!(!m[7][i], "定位分隔符 (7,{}) 应为白色", i);
assert!(!m[i][7], "定位分隔符 ({},7) 应为白色", i);
}
}
#[test]
fn test_qr_structure_dump() {
// 打印矩阵到 stdout(用于调试)
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
let size = qr.size() as usize;
let m = qr.modules();
println!("\n=== QR Matrix {}x{} v{} mask{} ===", size, size, qr.version.0, qr.mask);
for y in 0..size {
for x in 0..size {
print!("{}", if m[y][x] { "##" } else { " " });
}
println!();
}
// 统计
let dark: usize = m.iter().flatten().filter(|&&x| x).count();
let total = size * size;
println!("\n暗/总: {}/{} = {:.1}%", dark, total, dark as f64 / total as f64 * 100.0);
println!("尺寸: {}×{}", size, size);
println!("版本: {} 掩码: {} 纠错: {:?}", qr.version.0, qr.mask, qr.level);
}
#[test]
fn test_encode_simple_text() {
let config = QrConfig::default();