1#![cfg_attr(not(feature = "std"), no_std)]
26
27extern crate alloc;
28
29#[cfg(not(feature = "std"))]
30#[allow(unused_imports)]
31use alloc::{
32 borrow::ToOwned,
33 format,
34 string::{String, ToString},
35 vec,
36 vec::Vec,
37};
38
39use core::cmp::max;
40use core::fmt;
41use qrcode_core::As;
42pub use qrcode_core::Color;
43
44pub mod ansi;
45pub mod colors;
46#[cfg(feature = "image")]
47pub mod image;
48pub mod plugin;
49pub mod string;
50pub mod unicode;
51
52pub const MAX_BUFFER_BYTES: usize = 256 * 1024 * 1024;
59
60pub(crate) fn checked_area(width: u32, height: u32) -> Result<usize, RenderError> {
61 (width as usize).checked_mul(height as usize).ok_or(RenderError::OutputTooLarge)
62}
63
64pub(crate) fn check_buffer_size(bytes: usize) -> Result<(), RenderError> {
65 if bytes > MAX_BUFFER_BYTES || bytes > isize::MAX as usize { Err(RenderError::OutputTooLarge) } else { Ok(()) }
66}
67
68pub trait Pixel: Copy + Sized {
73 type Image: Sized + 'static;
75
76 type Canvas: Canvas<Pixel = Self, Image = Self::Image>;
79
80 fn default_unit_size() -> (u32, u32) {
82 (8, 8)
83 }
84
85 fn default_color(color: Color) -> Self;
87}
88
89pub trait StyledPixel: Pixel {
97 fn from_hex(hex: &str) -> Self;
100}
101
102pub trait RenderTemplate {
109 fn dark_color(&self) -> &str;
111
112 fn light_color(&self) -> &str;
114
115 fn module_size(&self) -> Option<(u32, u32)>;
117
118 fn quiet_zone(&self) -> bool;
120}
121
122pub trait Canvas: Sized {
124 type Pixel: Sized;
126 type Image: Sized;
128
129 fn new(width: u32, height: u32, dark_pixel: Self::Pixel, light_pixel: Self::Pixel) -> Self;
131
132 fn validate_dimensions(
144 _width: u32,
145 _height: u32,
146 _dark_pixel: &Self::Pixel,
147 _light_pixel: &Self::Pixel,
148 ) -> Result<(), RenderError> {
149 Ok(())
150 }
151
152 fn draw_dark_pixel(&mut self, x: u32, y: u32);
154
155 fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) {
158 for y in top..(top + height) {
159 for x in left..(left + width) {
160 self.draw_dark_pixel(x, y);
161 }
162 }
163 }
164
165 fn into_image(self) -> Self::Image;
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
175pub enum RenderError {
176 InvalidModuleSource {
178 width: usize,
180 height: usize,
182 len: usize,
184 },
185
186 ModuleSourceTooWide {
188 width: usize,
190 },
191
192 OutputTooLarge,
196}
197
198impl fmt::Display for RenderError {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 match self {
201 RenderError::InvalidModuleSource { width, height, len } => {
202 write!(f, "invalid module source dimensions: width={width}, height={height}, len={len}")
203 }
204 RenderError::ModuleSourceTooWide { width } => write!(f, "module source width {width} exceeds u32::MAX"),
205 RenderError::OutputTooLarge => f.write_str("rendered output exceeds coordinate or backend resource limits"),
206 }
207 }
208}
209
210#[cfg(feature = "std")]
211impl std::error::Error for RenderError {}
212
213pub struct Renderer<'a, P: Pixel> {
216 content: &'a [Color],
217 modules_count: u32, quiet_zone: u32,
219 module_size: (u32, u32),
220
221 dark_color: P,
222 light_color: P,
223 has_quiet_zone: bool,
224}
225
226impl<'a, P: Pixel> Renderer<'a, P> {
227 pub fn new(content: &'a [Color], modules_count: usize, quiet_zone: u32) -> Renderer<'a, P> {
232 assert_eq!(modules_count * modules_count, content.len());
233 Renderer {
234 content,
235 modules_count: modules_count.as_u32(),
236 quiet_zone,
237 module_size: P::default_unit_size(),
238 dark_color: P::default_color(Color::Dark),
239 light_color: P::default_color(Color::Light),
240 has_quiet_zone: true,
241 }
242 }
243
244 pub fn from_source<C>(source: &'a C, quiet_zone: u32) -> Renderer<'a, P>
256 where
257 C: qrcode_core::ModuleSource + ?Sized,
258 {
259 match Self::try_from_source(source, quiet_zone) {
260 Ok(renderer) => renderer,
261 Err(err) => panic!("{err}"),
262 }
263 }
264
265 pub fn from_symbol<S>(symbol: &'a S) -> Renderer<'a, P>
274 where
275 S: qrcode_core::QrSymbol + ?Sized,
276 {
277 Self::from_source(symbol, symbol.quiet_zone())
278 }
279
280 pub fn try_from_source<C>(source: &'a C, quiet_zone: u32) -> Result<Renderer<'a, P>, RenderError>
292 where
293 C: qrcode_core::ModuleSource + ?Sized,
294 {
295 let width = source.width();
296 let height = source.height();
297 let len = source.modules().len();
298 let Some(expected_len) = width.checked_mul(height) else {
299 return Err(RenderError::InvalidModuleSource { width, height, len });
300 };
301 if width == 0 || width != height || len != expected_len {
302 return Err(RenderError::InvalidModuleSource { width, height, len });
303 }
304 if width > u32::MAX as usize {
305 return Err(RenderError::ModuleSourceTooWide { width });
306 }
307 Ok(Self::new(source.modules(), width, quiet_zone))
308 }
309
310 pub fn try_from_symbol<S>(symbol: &'a S) -> Result<Renderer<'a, P>, RenderError>
321 where
322 S: qrcode_core::QrSymbol + ?Sized,
323 {
324 Self::try_from_source(symbol, symbol.quiet_zone())
325 }
326
327 pub fn dark_color(&mut self, color: P) -> &mut Self {
329 self.dark_color = color;
330 self
331 }
332
333 pub fn light_color(&mut self, color: P) -> &mut Self {
335 self.light_color = color;
336 self
337 }
338
339 pub fn quiet_zone(&mut self, has_quiet_zone: bool) -> &mut Self {
341 self.has_quiet_zone = has_quiet_zone;
342 self
343 }
344
345 pub fn module_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
347 self.module_size = (max(width, 1), max(height, 1));
348 self
349 }
350
351 pub fn min_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
360 let quiet_zone = if self.has_quiet_zone { 2 * u64::from(self.quiet_zone) } else { 0 };
361 let width_in_modules = (u64::from(self.modules_count) + quiet_zone).max(1);
362 let unit_width = u64::from(width).div_ceil(width_in_modules) as u32;
363 let unit_height = u64::from(height).div_ceil(width_in_modules) as u32;
364 self.module_dimensions(unit_width, unit_height)
365 }
366
367 pub fn max_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
379 let quiet_zone = if self.has_quiet_zone { 2 * u64::from(self.quiet_zone) } else { 0 };
380 let width_in_modules = (u64::from(self.modules_count) + quiet_zone).max(1);
381 let unit_width = (u64::from(width) / width_in_modules) as u32;
382 let unit_height = (u64::from(height) / width_in_modules) as u32;
383 self.module_dimensions(unit_width, unit_height)
384 }
385
386 pub fn for_web(&mut self) -> &mut Self {
391 self.min_dimensions(200, 200)
392 }
393
394 pub fn for_print(&mut self, dpi: u32) -> &mut Self {
403 self.min_dimensions(dpi.max(72), dpi.max(72))
404 }
405
406 pub fn for_social(&mut self, platform: &str) -> &mut Self {
418 let size = match platform {
419 "twitter" | "x" => 400,
420 "facebook" | "fb" => 600,
421 "instagram" | "ig" => 1080,
422 "wechat" | "weixin" => 600,
423 _ => 400,
424 };
425 self.min_dimensions(size, size)
426 }
427
428 pub fn try_build(&self) -> Result<P::Image, RenderError> {
437 let w = self.modules_count;
438 if w == 0 {
439 return Err(RenderError::InvalidModuleSource { width: 0, height: 0, len: self.content.len() });
440 }
441 let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 };
442 let quiet = qz.checked_mul(2).ok_or(RenderError::OutputTooLarge)?;
443 let width = w.checked_add(quiet).ok_or(RenderError::OutputTooLarge)?;
444
445 let (mw, mh) = self.module_size;
446 let real_width = width.checked_mul(mw).ok_or(RenderError::OutputTooLarge)?;
447 let real_height = width.checked_mul(mh).ok_or(RenderError::OutputTooLarge)?;
448
449 P::Canvas::validate_dimensions(real_width, real_height, &self.dark_color, &self.light_color)?;
450 let mut canvas = P::Canvas::new(real_width, real_height, self.dark_color, self.light_color);
451 for (y, row) in self.content.chunks_exact(w as usize).enumerate() {
452 let top = (y as u32 + qz) * mh;
453 for (x, &module) in row.iter().enumerate() {
454 if module != Color::Light {
455 canvas.draw_dark_rect((x as u32 + qz) * mw, top, mw, mh);
456 }
457 }
458 }
459
460 Ok(canvas.into_image())
461 }
462
463 pub fn build(&self) -> P::Image {
471 self.try_build().unwrap_or_else(|err| panic!("{err}"))
472 }
473}
474
475impl<C, P> qrcode_core::Renderer<C> for Renderer<'_, P>
476where
477 C: qrcode_core::ModuleSource + ?Sized,
478 P: Pixel,
479{
480 type Output = P::Image;
481 type Error = RenderError;
482
483 fn render(&self, code: &C) -> Result<Self::Output, Self::Error> {
484 let mut renderer = Renderer::try_from_source(code, self.quiet_zone)?;
485 renderer.module_size = self.module_size;
486 renderer.dark_color = self.dark_color;
487 renderer.light_color = self.light_color;
488 renderer.has_quiet_zone = self.has_quiet_zone;
489 renderer.try_build()
490 }
491}
492
493impl<'a, P: Pixel> qrcode_core::Builder for &'a Renderer<'a, P> {
494 type Output = P::Image;
495 type Error = RenderError;
496
497 fn build(self) -> Result<Self::Output, Self::Error> {
498 self.try_build()
499 }
500}
501
502impl<'a, P: StyledPixel> Renderer<'a, P> {
503 pub fn template<T: RenderTemplate>(mut self, tmpl: &T) -> Self {
507 self.dark_color = P::from_hex(tmpl.dark_color());
508 self.light_color = P::from_hex(tmpl.light_color());
509 if let Some((w, h)) = tmpl.module_size() {
510 self.module_dimensions(w, h);
511 }
512 self.has_quiet_zone = tmpl.quiet_zone();
513 self
514 }
515}
516
517#[cfg(test)]
520mod tests {
521 use super::{Canvas, Pixel, RenderError, RenderTemplate, Renderer};
522 use qrcode_core::{Color, EcLevel, ModuleSource, QrSymbol, Renderer as CoreRenderer, Version};
523
524 #[derive(Clone, Copy)]
525 struct VectorPixel;
526
527 struct VectorCanvas(u32, u32);
528
529 impl Pixel for VectorPixel {
530 type Image = (u32, u32);
531 type Canvas = VectorCanvas;
532
533 fn default_color(_color: Color) -> Self {
534 Self
535 }
536 }
537
538 impl Canvas for VectorCanvas {
539 type Pixel = VectorPixel;
540 type Image = (u32, u32);
541
542 fn new(width: u32, height: u32, _dark: VectorPixel, _light: VectorPixel) -> Self {
543 Self(width, height)
544 }
545
546 fn draw_dark_pixel(&mut self, _x: u32, _y: u32) {}
547
548 fn draw_dark_rect(&mut self, _left: u32, _top: u32, _width: u32, _height: u32) {}
549
550 fn into_image(self) -> Self::Image {
551 (self.0, self.1)
552 }
553 }
554
555 #[derive(Clone, Copy)]
556 struct RejectingPixel;
557
558 struct RejectingCanvas;
559
560 impl Pixel for RejectingPixel {
561 type Image = ();
562 type Canvas = RejectingCanvas;
563
564 fn default_color(_color: Color) -> Self {
565 Self
566 }
567 }
568
569 impl Canvas for RejectingCanvas {
570 type Pixel = RejectingPixel;
571 type Image = ();
572
573 fn new(_width: u32, _height: u32, _dark: RejectingPixel, _light: RejectingPixel) -> Self {
574 panic!("rejected dimensions must not reach allocation")
575 }
576
577 fn validate_dimensions(
578 _width: u32,
579 _height: u32,
580 _dark: &RejectingPixel,
581 _light: &RejectingPixel,
582 ) -> Result<(), RenderError> {
583 Err(RenderError::OutputTooLarge)
584 }
585
586 fn draw_dark_pixel(&mut self, _x: u32, _y: u32) {}
587
588 fn into_image(self) {}
589 }
590
591 #[test]
592 fn default_canvas_validation_preserves_large_vector_dimensions() {
593 let modules = [Color::Dark];
594 assert_eq!(
595 Renderer::<VectorPixel>::new(&modules, 1, 0).module_dimensions(u32::MAX, u32::MAX).try_build(),
596 Ok((u32::MAX, u32::MAX))
597 );
598 }
599
600 #[test]
601 fn try_build_validates_backend_limits_before_constructing_canvas() {
602 let modules = [Color::Light];
603 assert_eq!(Renderer::<RejectingPixel>::new(&modules, 1, 0).try_build(), Err(RenderError::OutputTooLarge));
604 }
605
606 struct BadSource {
607 modules: [Color; 4],
608 }
609
610 impl ModuleSource for BadSource {
611 fn get(&self, x: usize, y: usize) -> Color {
612 self.modules[y * self.width() + x]
613 }
614
615 fn width(&self) -> usize {
616 3
617 }
618
619 fn height(&self) -> usize {
620 2
621 }
622
623 fn modules(&self) -> &[Color] {
624 &self.modules
625 }
626 }
627
628 struct SymbolSource {
629 version: Version,
630 modules: [Color; 1],
631 }
632
633 impl ModuleSource for SymbolSource {
634 fn get(&self, _x: usize, _y: usize) -> Color {
635 self.modules[0]
636 }
637
638 fn width(&self) -> usize {
639 1
640 }
641
642 fn height(&self) -> usize {
643 1
644 }
645
646 fn modules(&self) -> &[Color] {
647 &self.modules
648 }
649 }
650
651 impl QrSymbol for SymbolSource {
652 fn version(&self) -> Version {
653 self.version
654 }
655
656 fn error_correction_level(&self) -> EcLevel {
657 EcLevel::M
658 }
659 }
660
661 #[test]
662 fn try_from_source_returns_error_for_invalid_dimensions() {
663 let source = BadSource { modules: [Color::Dark; 4] };
664
665 let result = Renderer::<char>::try_from_source(&source, 0);
666 assert!(matches!(result, Err(RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 })));
667 }
668
669 #[test]
670 fn core_renderer_returns_error_for_invalid_source() {
671 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
672 let renderer = Renderer::<char>::new(&modules, 2, 0);
673 let source = BadSource { modules: [Color::Dark; 4] };
674
675 assert_eq!(
676 CoreRenderer::render(&renderer, &source).unwrap_err(),
677 RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 }
678 );
679 }
680
681 #[test]
682 fn core_renderer_matches_direct_builder_output() {
683 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
684 let source = qrcode_core::ModuleView::new(&modules, 2).unwrap();
685 let mut renderer = Renderer::<char>::new(&modules, 2, 1);
686 renderer.dark_color('#').light_color('.');
687
688 assert_eq!(CoreRenderer::render(&renderer, &source).unwrap(), renderer.build());
689 }
690
691 #[test]
692 fn core_builder_returns_the_same_output_as_inherent_builder() {
693 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
694 let mut renderer = Renderer::<char>::new(&modules, 2, 1);
695 renderer.dark_color('#').light_color('.').module_dimensions(2, 3);
696 let expected = renderer.build();
697
698 assert_eq!(qrcode_core::Builder::build(&renderer), Ok(expected));
699 }
700
701 #[test]
702 fn core_builder_returns_dimension_errors_without_panicking() {
703 let modules = [Color::Dark];
704 for (quiet_zone, module_width, module_height) in [(u32::MAX, 1, 1), (1, u32::MAX, 1), (1, 1, u32::MAX)] {
705 let mut renderer = Renderer::<char>::new(&modules, 1, quiet_zone);
706 renderer.module_dimensions(module_width, module_height);
707
708 assert_eq!(qrcode_core::Builder::build(&renderer), Err(RenderError::OutputTooLarge));
709 }
710 }
711
712 #[test]
713 fn empty_module_grid_returns_errors_before_creating_a_canvas() {
714 let mut renderer = Renderer::<char>::new(&[], 0, 0);
715 let error = RenderError::InvalidModuleSource { width: 0, height: 0, len: 0 };
716
717 assert_eq!(renderer.try_build(), Err(error));
718 assert_eq!(qrcode_core::Builder::build(&renderer), Err(error));
719 assert_eq!(renderer.min_dimensions(100, 100).try_build(), Err(error));
720 assert_eq!(renderer.max_dimensions(100, 100).try_build(), Err(error));
721 }
722
723 #[test]
724 fn dimension_presets_defer_oversized_quiet_zone_errors_to_try_build() {
725 let modules = [Color::Dark; 4];
726 for quiet_zone in [u32::MAX, u32::MAX / 2] {
727 let mut renderer = Renderer::<char>::new(&modules, 2, quiet_zone);
728
729 assert_eq!(renderer.min_dimensions(100, 100).try_build(), Err(RenderError::OutputTooLarge));
730 assert_eq!(renderer.max_dimensions(100, 100).try_build(), Err(RenderError::OutputTooLarge));
731 }
732 }
733
734 #[test]
735 fn dimension_presets_ignore_a_disabled_quiet_zone() {
736 let modules = [Color::Dark];
737 let mut renderer = Renderer::<char>::new(&modules, 1, u32::MAX);
738 renderer.quiet_zone(false).dark_color('#');
739
740 assert_eq!(renderer.min_dimensions(1, 1).try_build(), Ok("#".into()));
741 assert_eq!(renderer.max_dimensions(1, 1).try_build(), Ok("#".into()));
742 }
743
744 #[test]
745 fn template_module_dimensions_follow_the_module_size_setter() {
746 struct ModuleSizeTemplate((u32, u32));
747
748 impl RenderTemplate for ModuleSizeTemplate {
749 fn dark_color(&self) -> &str {
750 "#000"
751 }
752
753 fn light_color(&self) -> &str {
754 "#fff"
755 }
756
757 fn module_size(&self) -> Option<(u32, u32)> {
758 Some(self.0)
759 }
760
761 fn quiet_zone(&self) -> bool {
762 false
763 }
764 }
765
766 let modules = [Color::Dark];
767 for dimensions in [(0, 0), (0, 2), (2, 0), (2, 3)] {
768 let template = ModuleSizeTemplate(dimensions);
769 let renderer = Renderer::<super::ansi::Color>::new(&modules, 1, 0).template(&template);
770 let mut manual = Renderer::<super::ansi::Color>::new(&modules, 1, 0);
771 manual.module_dimensions(dimensions.0, dimensions.1).quiet_zone(false);
772
773 assert_eq!(renderer.try_build(), manual.try_build());
774 }
775 }
776
777 #[test]
778 fn try_build_rejects_overflowing_dimensions() {
779 let modules = [Color::Dark];
780 let mut renderer = Renderer::<char>::new(&modules, 1, u32::MAX);
781 renderer.module_dimensions(u32::MAX, 1);
782
783 assert_eq!(renderer.try_build(), Err(RenderError::OutputTooLarge));
784 }
785
786 #[test]
787 fn build_keeps_quiet_zone_while_scanning_only_source_modules() {
788 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
789 let mut renderer = Renderer::<char>::new(&modules, 2, 1);
790 renderer.dark_color('#').light_color('.').module_dimensions(1, 1);
791
792 assert_eq!(renderer.build(), "....\n.#..\n..#.\n....");
793 }
794
795 #[test]
796 fn from_symbol_uses_normal_qr_quiet_zone() {
797 let source = SymbolSource { version: Version::Normal(1), modules: [Color::Dark] };
798
799 let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
800
801 assert_eq!(output.lines().next().map(str::len), Some(9));
802 }
803
804 #[test]
805 fn from_symbol_uses_micro_qr_quiet_zone() {
806 let source = SymbolSource { version: Version::Micro(1), modules: [Color::Dark] };
807
808 let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
809
810 assert_eq!(output.lines().next().map(str::len), Some(5));
811 }
812}