1use image::{DynamicImage, ImageBuffer, Rgba, RgbaImage};
50use qrcode::render::svg;
51use qrcode::types::Color as QrColor;
52use qrcode::{EcLevel, QrCode};
53use thiserror::Error;
54
55#[derive(Debug, Error)]
61pub enum QrCodeError {
62 #[error("二维码生成失败: {0}")]
64 Generation(String),
65
66 #[error("数据编码失败: {0}")]
68 Encoding(String),
69
70 #[error("IO 错误: {0}")]
72 Io(String),
73}
74
75impl From<std::io::Error> for QrCodeError {
76 fn from(err: std::io::Error) -> Self {
77 QrCodeError::Io(err.to_string())
78 }
79}
80
81impl From<image::ImageError> for QrCodeError {
82 fn from(err: image::ImageError) -> Self {
83 QrCodeError::Io(err.to_string())
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
95pub enum ErrorCorrectionLevel {
96 Low,
98 #[default]
100 Medium,
101 Quartile,
103 High,
105}
106
107impl ErrorCorrectionLevel {
108 fn to_ec_level(self) -> EcLevel {
110 match self {
111 Self::Low => EcLevel::L,
112 Self::Medium => EcLevel::M,
113 Self::Quartile => EcLevel::Q,
114 Self::High => EcLevel::H,
115 }
116 }
117
118 pub fn as_str(self) -> &'static str {
120 match self {
121 Self::Low => "low",
122 Self::Medium => "medium",
123 Self::Quartile => "quartile",
124 Self::High => "high",
125 }
126 }
127}
128
129impl std::fmt::Display for ErrorCorrectionLevel {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 f.write_str(self.as_str())
132 }
133}
134
135#[derive(Debug, Clone)]
143pub struct QrCodeConfig {
144 pub size: u32,
146 pub margin: u32,
148 pub foreground_color: [u8; 3],
150 pub background_color: [u8; 3],
152 pub error_correction_level: ErrorCorrectionLevel,
154}
155
156impl Default for QrCodeConfig {
157 fn default() -> Self {
158 Self {
159 size: 200,
160 margin: 10,
161 foreground_color: [0, 0, 0],
162 background_color: [255, 255, 255],
163 error_correction_level: ErrorCorrectionLevel::Medium,
164 }
165 }
166}
167
168impl QrCodeConfig {
169 pub fn new() -> Self {
177 Self::default()
178 }
179
180 pub fn with_size(mut self, size: u32) -> Self {
182 self.size = size;
183 self
184 }
185
186 pub fn with_margin(mut self, margin: u32) -> Self {
188 self.margin = margin;
189 self
190 }
191
192 pub fn with_foreground_color(mut self, color: [u8; 3]) -> Self {
194 self.foreground_color = color;
195 self
196 }
197
198 pub fn with_background_color(mut self, color: [u8; 3]) -> Self {
200 self.background_color = color;
201 self
202 }
203
204 pub fn with_error_correction_level(mut self, level: ErrorCorrectionLevel) -> Self {
206 self.error_correction_level = level;
207 self
208 }
209
210 fn color_to_hex(color: [u8; 3]) -> String {
212 format!("#{:02x}{:02x}{:02x}", color[0], color[1], color[2])
213 }
214}
215
216#[derive(Debug, Clone)]
248pub struct QrCodeGenerator {
249 config: QrCodeConfig,
251}
252
253impl QrCodeGenerator {
254 pub fn new() -> Self {
256 Self {
257 config: QrCodeConfig::default(),
258 }
259 }
260
261 pub fn with_config(config: QrCodeConfig) -> Self {
263 Self { config }
264 }
265
266 pub fn config(&self) -> &QrCodeConfig {
268 &self.config
269 }
270
271 pub fn generate_matrix(&self, data: &str) -> Result<Vec<Vec<bool>>, QrCodeError> {
283 if data.is_empty() {
284 return Err(QrCodeError::Encoding("数据不能为空".to_string()));
285 }
286
287 let code = QrCode::with_error_correction_level(
288 data.as_bytes(),
289 self.config.error_correction_level.to_ec_level(),
290 )
291 .map_err(|e| QrCodeError::Generation(format!("二维码编码失败: {e}")))?;
292
293 let width = code.width();
294 let matrix = (0..width)
295 .map(|y| (0..width).map(|x| code[(x, y)] == QrColor::Dark).collect())
296 .collect();
297
298 Ok(matrix)
299 }
300
301 pub fn generate_png(&self, data: &str) -> Result<Vec<u8>, QrCodeError> {
314 let matrix = self.generate_matrix(data)?;
315 let png_bytes = self.render_matrix_to_png(&matrix)?;
316 Ok(png_bytes)
317 }
318
319 pub fn generate_svg(&self, data: &str) -> Result<String, QrCodeError> {
332 if data.is_empty() {
333 return Err(QrCodeError::Encoding("数据不能为空".to_string()));
334 }
335
336 let code = QrCode::with_error_correction_level(
337 data.as_bytes(),
338 self.config.error_correction_level.to_ec_level(),
339 )
340 .map_err(|e| QrCodeError::Generation(format!("二维码编码失败: {e}")))?;
341
342 let fg_hex = QrCodeConfig::color_to_hex(self.config.foreground_color);
343 let bg_hex = QrCodeConfig::color_to_hex(self.config.background_color);
344
345 let svg_string = code
346 .render::<svg::Color>()
347 .dark_color(svg::Color(&fg_hex))
348 .light_color(svg::Color(&bg_hex))
349 .quiet_zone(self.config.margin > 0)
350 .min_dimensions(self.config.size, self.config.size)
351 .build();
352
353 Ok(svg_string)
354 }
355
356 fn render_matrix_to_png(&self, matrix: &[Vec<bool>]) -> Result<Vec<u8>, QrCodeError> {
361 let matrix_width = matrix.len();
362 if matrix_width == 0 {
363 return Err(QrCodeError::Generation("矩阵为空".to_string()));
364 }
365
366 let total_size = self.config.size;
367 let margin = self.config.margin;
368
369 let qr_area = total_size
371 .checked_sub(margin.saturating_mul(2))
372 .filter(|&v| v > 0)
373 .ok_or_else(|| {
374 QrCodeError::Generation(format!(
375 "尺寸不足以容纳边距: size={total_size}, margin={margin}"
376 ))
377 })?;
378
379 let module_size = (qr_area / matrix_width as u32).max(1);
381
382 let [fr, fg, fb] = self.config.foreground_color;
383 let [br, bg, bb] = self.config.background_color;
384 let fg_pixel = Rgba([fr, fg, fb, 255]);
385 let bg_pixel = Rgba([br, bg, bb, 255]);
386
387 let mut img: RgbaImage = ImageBuffer::from_pixel(total_size, total_size, bg_pixel);
389
390 for (y, row) in matrix.iter().enumerate() {
392 for (x, &is_dark) in row.iter().enumerate() {
393 if is_dark {
394 let start_x = margin + (x as u32) * module_size;
395 let start_y = margin + (y as u32) * module_size;
396 for dy in 0..module_size {
397 for dx in 0..module_size {
398 let px = start_x + dx;
399 let py = start_y + dy;
400 if px < total_size && py < total_size {
401 img.put_pixel(px, py, fg_pixel);
402 }
403 }
404 }
405 }
406 }
407 }
408
409 let dynamic = DynamicImage::ImageRgba8(img);
411 let mut bytes = Vec::new();
412 let mut cursor = std::io::Cursor::new(&mut bytes);
413 dynamic.write_to(&mut cursor, image::ImageFormat::Png)?;
414 Ok(bytes)
415 }
416}
417
418impl Default for QrCodeGenerator {
419 fn default() -> Self {
420 Self::new()
421 }
422}
423
424#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
438 fn test_qr_code_config_default() {
439 let config = QrCodeConfig::default();
440 assert_eq!(config.size, 200);
441 assert_eq!(config.margin, 10);
442 assert_eq!(config.foreground_color, [0, 0, 0]);
443 assert_eq!(config.background_color, [255, 255, 255]);
444 assert_eq!(config.error_correction_level, ErrorCorrectionLevel::Medium);
445 }
446
447 #[test]
449 fn test_qr_code_config_builder() {
450 let config = QrCodeConfig::new()
451 .with_size(300)
452 .with_margin(20)
453 .with_foreground_color([255, 0, 0])
454 .with_background_color([0, 0, 255])
455 .with_error_correction_level(ErrorCorrectionLevel::High);
456
457 assert_eq!(config.size, 300);
458 assert_eq!(config.margin, 20);
459 assert_eq!(config.foreground_color, [255, 0, 0]);
460 assert_eq!(config.background_color, [0, 0, 255]);
461 assert_eq!(config.error_correction_level, ErrorCorrectionLevel::High);
462 }
463
464 #[test]
470 fn test_error_correction_level() {
471 assert_eq!(
473 ErrorCorrectionLevel::default(),
474 ErrorCorrectionLevel::Medium
475 );
476
477 assert_eq!(ErrorCorrectionLevel::Low.as_str(), "low");
479 assert_eq!(ErrorCorrectionLevel::Medium.as_str(), "medium");
480 assert_eq!(ErrorCorrectionLevel::Quartile.as_str(), "quartile");
481 assert_eq!(ErrorCorrectionLevel::High.as_str(), "high");
482
483 assert_eq!(format!("{}", ErrorCorrectionLevel::Low), "low");
485 assert_eq!(format!("{}", ErrorCorrectionLevel::High), "high");
486
487 assert_eq!(ErrorCorrectionLevel::Low.to_ec_level(), EcLevel::L);
489 assert_eq!(ErrorCorrectionLevel::Medium.to_ec_level(), EcLevel::M);
490 assert_eq!(ErrorCorrectionLevel::Quartile.to_ec_level(), EcLevel::Q);
491 assert_eq!(ErrorCorrectionLevel::High.to_ec_level(), EcLevel::H);
492 }
493
494 #[test]
500 fn test_qr_code_generator_default() {
501 let generator = QrCodeGenerator::new();
502 assert_eq!(generator.config().size, 200);
503 assert_eq!(generator.config().margin, 10);
504 assert_eq!(
505 generator.config().error_correction_level,
506 ErrorCorrectionLevel::Medium
507 );
508 }
509
510 #[test]
512 fn test_qr_code_generator_with_config() {
513 let config = QrCodeConfig::new()
514 .with_size(400)
515 .with_margin(15)
516 .with_error_correction_level(ErrorCorrectionLevel::Quartile);
517 let generator = QrCodeGenerator::with_config(config);
518 assert_eq!(generator.config().size, 400);
519 assert_eq!(generator.config().margin, 15);
520 assert_eq!(
521 generator.config().error_correction_level,
522 ErrorCorrectionLevel::Quartile
523 );
524 }
525
526 #[test]
532 fn test_generate_matrix_basic() {
533 let generator = QrCodeGenerator::new();
534 let matrix = generator.generate_matrix("Hello, World!").unwrap();
535
536 assert!(!matrix.is_empty(), "矩阵不能为空");
537 let width = matrix.len();
538 for row in &matrix {
539 assert_eq!(row.len(), width, "矩阵必须是正方形");
540 }
541 }
542
543 #[test]
545 fn test_generate_matrix_empty_data() {
546 let generator = QrCodeGenerator::new();
547 let result = generator.generate_matrix("");
548 assert!(result.is_err(), "空数据应返回错误");
549 match result {
550 Err(QrCodeError::Encoding(_)) => {}
551 other => panic!("期望 Encoding 错误,得到: {other:?}"),
552 }
553 }
554
555 #[test]
561 fn test_generate_png_basic() {
562 let generator = QrCodeGenerator::new();
563 let png = generator.generate_png("https://example.com").unwrap();
564 assert!(!png.is_empty(), "PNG 字节流不能为空");
565
566 assert_eq!(png[0], 0x89, "PNG magic byte 0");
568 assert_eq!(png[1], 0x50, "PNG magic byte 1 ('P')");
569 assert_eq!(png[2], 0x4E, "PNG magic byte 2 ('N')");
570 assert_eq!(png[3], 0x47, "PNG magic byte 3 ('G')");
571 assert_eq!(png[4], 0x0D, "PNG magic byte 4 (CR)");
572 assert_eq!(png[5], 0x0A, "PNG magic byte 5 (LF)");
573 assert_eq!(png[6], 0x1A, "PNG magic byte 6");
574 assert_eq!(png[7], 0x0A, "PNG magic byte 7 (LF)");
575 }
576
577 #[test]
579 fn test_generate_png_valid_output() {
580 let generator = QrCodeGenerator::new();
581 let png = generator.generate_png("test data 12345").unwrap();
582 let png_signature: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
584 assert_eq!(&png[..8], &png_signature, "PNG 头部签名必须匹配");
585 assert!(png.len() > 24, "PNG 数据长度应超过头部+IHDR");
587 }
588
589 #[test]
595 fn test_generate_svg_basic() {
596 let generator = QrCodeGenerator::new();
597 let svg = generator.generate_svg("Hello SVG").unwrap();
598 assert!(!svg.is_empty(), "SVG 字符串不能为空");
599 assert!(svg.contains("<svg"), "SVG 必须包含 <svg> 标签");
600 assert!(svg.contains("</svg>"), "SVG 必须包含 </svg> 闭合标签");
601 }
602
603 #[test]
605 fn test_generate_svg_contains_data() {
606 let generator = QrCodeGenerator::new();
607 let svg = generator.generate_svg("Data content test 12345").unwrap();
608 assert!(
610 svg.contains("<rect") || svg.contains("<path"),
611 "SVG 必须包含 rect 或 path 元素来表示二维码模块"
612 );
613 assert!(svg.contains("#000000"), "SVG 应包含默认前景色 #000000");
615 }
616
617 #[test]
623 fn test_generate_different_data_different_matrix() {
624 let generator = QrCodeGenerator::new();
625 let matrix1 = generator.generate_matrix("data one").unwrap();
626 let matrix2 = generator.generate_matrix("data two").unwrap();
627
628 assert_ne!(matrix1, matrix2, "不同数据应生成不同的二维码矩阵");
630 }
631
632 #[test]
634 fn test_generate_high_error_correction() {
635 let config = QrCodeConfig::new().with_error_correction_level(ErrorCorrectionLevel::High);
636 let generator = QrCodeGenerator::with_config(config);
637
638 let png = generator.generate_png("High EC test").unwrap();
640 assert!(!png.is_empty(), "高容错 PNG 不应为空");
641
642 let svg = generator.generate_svg("High EC test").unwrap();
643 assert!(svg.contains("<svg"), "高容错 SVG 应包含 <svg> 标签");
644
645 let matrix = generator.generate_matrix("High EC test").unwrap();
647 assert!(!matrix.is_empty(), "高容错矩阵不应为空");
648
649 assert_eq!(
651 generator.config().error_correction_level,
652 ErrorCorrectionLevel::High
653 );
654 }
655}