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 { // 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::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] { &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 { 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); } } }