195 lines
5.6 KiB
Rust
195 lines
5.6 KiB
Rust
use crate::ecc::reed_solomon;
|
|
use crate::encoder::bitstream::build_codewords;
|
|
use crate::encoder::segment::{segment_bit_length, segment_text};
|
|
use crate::matrix::grid::Matrix;
|
|
use crate::matrix::mask::best_mask;
|
|
use crate::matrix::patterns::{
|
|
encode_format_info, encode_version_info, place_alignment_patterns, place_finder_patterns,
|
|
place_format_info, place_timing_patterns, place_version_info, reserve_format_areas,
|
|
reserve_version_areas,
|
|
};
|
|
use crate::matrix::placement::place_data;
|
|
use crate::version::{get_data_capacity, EcLevel, Version};
|
|
|
|
/// 版本选择模式
|
|
#[derive(Debug, Clone)]
|
|
pub enum VersionMode {
|
|
Auto,
|
|
Fixed(u8),
|
|
}
|
|
|
|
/// QR 码配置
|
|
#[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,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 生成的 QR 码
|
|
pub struct QrCode {
|
|
pub version: Version,
|
|
pub level: EcLevel,
|
|
pub mask: u8,
|
|
matrix: Matrix,
|
|
pub 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("无效版本号 (1-40)")?,
|
|
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 码最大容量".to_string())?
|
|
}
|
|
};
|
|
|
|
// 3. 构建数据码字
|
|
let data = build_codewords(text, version, config.level);
|
|
|
|
// 4. 纠错编码 — 分组并计算 RS 纠错码
|
|
let ec_info = version.ec_info(config.level);
|
|
let mut blocks: Vec<Vec<u8>> = Vec::new();
|
|
let mut pos = 0usize;
|
|
for binfo in &ec_info.blocks {
|
|
for _ in 0..binfo.count {
|
|
let end = pos + binfo.data_codewords as usize;
|
|
if end > data.len() {
|
|
return Err("内部错误: 数据码字不足".into());
|
|
}
|
|
blocks.push(data[pos..end].to_vec());
|
|
pos = end;
|
|
}
|
|
}
|
|
let final_codewords = reed_solomon::interleave(&blocks, ec_info.ec_per_block);
|
|
|
|
// 5. 构建矩阵 + 放置功能图案
|
|
let mut matrix = Matrix::new(version.size());
|
|
place_finder_patterns(&mut matrix);
|
|
place_timing_patterns(&mut matrix);
|
|
place_alignment_patterns(&mut matrix, version.alignment_positions());
|
|
reserve_format_areas(&mut matrix);
|
|
if version.0 >= 7 {
|
|
reserve_version_areas(&mut matrix);
|
|
}
|
|
|
|
// 6. 蛇形放置数据
|
|
place_data(&mut matrix, &final_codewords);
|
|
|
|
// 7. 掩码评分 → 选最佳
|
|
let (best_idx, best_matrix) = best_mask(&matrix);
|
|
|
|
// 8. 写入格式信息
|
|
let format = encode_format_info(config.level.indicator_bits(), best_idx);
|
|
let mut final_matrix = best_matrix;
|
|
place_format_info(&mut final_matrix, format);
|
|
|
|
// 9. 写入版本信息(版本 ≥ 7)
|
|
if version.0 >= 7 {
|
|
let ver_info = encode_version_info(version.0);
|
|
place_version_info(&mut final_matrix, ver_info);
|
|
}
|
|
|
|
Ok(QrCode {
|
|
version,
|
|
level: config.level,
|
|
mask: best_idx,
|
|
matrix: final_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)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_encode_simple() {
|
|
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
|
|
assert_eq!(qr.version.0, 1);
|
|
assert_eq!(qr.size(), 21);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_numeric() {
|
|
let qr = QrCode::encode("1234567890", QrConfig::default()).unwrap();
|
|
assert_eq!(qr.size(), 21);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_empty_fails() {
|
|
assert!(QrCode::encode("", QrConfig::default()).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_fixed_version() {
|
|
let config = QrConfig {
|
|
version: VersionMode::Fixed(3),
|
|
..Default::default()
|
|
};
|
|
let qr = QrCode::encode("FIXED VERSION", config).unwrap();
|
|
assert_eq!(qr.version.0, 3);
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
}
|
|
}
|