Skip to main content

qrcode_render/
lib.rs

1//! Rendering pipeline for converting QR codes into visual output.
2//!
3//! This module provides the [`Pixel`] and [`Canvas`] traits that abstract over
4//! different output formats, and the [`Renderer`] builder that drives the
5//! conversion from QR code data to a final image.
6//!
7//! # Supported formats
8//!
9//! | Module  | Feature | Output type |
10//! |---------|---------|-------------|
11//! | `image` | `image` | PNG/JPEG via the `image` crate |
12//! | `svg`   | `svg`   | SVG XML string |
13//! | `eps`   | `eps`   | Encapsulated PostScript |
14//! | `html`  | `html`  | HTML table or CSS Grid |
15//! | `pic`   | `pic`   | PIC (troff) macros |
16//! | `string`| —       | Plain text with custom characters |
17//! | `unicode`| —      | Unicode block-element rendering |
18//!
19//! # Custom rendering
20//!
21//! Implement [`Pixel`] for your own type to render into a custom format.
22//! The [`Pixel`] trait defines how to create dark/light pixels and how to
23//! finalize a canvas into a concrete image.
24
25#![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
52/// Maximum combined buffer and output size estimated by the built-in buffered
53/// rendering backends (256 MiB).
54///
55/// This guards against excessive render requests, not allocator failures under
56/// memory pressure. Vector backends and third-party canvases choose their own
57/// limits through [`Canvas::validate_dimensions`].
58pub 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
68//------------------------------------------------------------------------------
69//{{{ Pixel trait
70
71/// Abstraction of an image pixel.
72pub trait Pixel: Copy + Sized {
73    /// Type of the finalized image.
74    type Image: Sized + 'static;
75
76    /// The type that stores an intermediate buffer before finalizing to a
77    /// concrete image
78    type Canvas: Canvas<Pixel = Self, Image = Self::Image>;
79
80    /// Obtains the default module size. The result must be at least 1×1.
81    fn default_unit_size() -> (u32, u32) {
82        (8, 8)
83    }
84
85    /// Obtains the default pixel color when a module is dark or light.
86    fn default_color(color: Color) -> Self;
87}
88
89/// A [`Pixel`] constructible from a CSS-style hex color string (`"#rrggbb"` or
90/// `"#rgb"`), used by `Renderer::template` to apply a `QrTemplate`.
91///
92/// Implemented for the owned, styled backends (image RGB/RGBA, EPS, PDF, ANSI).
93/// The borrowing backends (`svg::Color`, `html::Color`) are not `StyledPixel`
94/// because their color borrows from the input and can't be stored generically;
95/// apply those colors manually instead.
96pub trait StyledPixel: Pixel {
97    /// Builds a pixel from a hex color string, falling back to black on an
98    /// unparseable value.
99    fn from_hex(hex: &str) -> Self;
100}
101
102/// Styling data that can be applied to a [`Renderer`] with
103/// [`Renderer::template`].
104///
105/// The facade crate implements this for its `QrTemplate`, while downstream
106/// crates can provide their own lightweight template types without depending on
107/// the facade.
108pub trait RenderTemplate {
109    /// Dark module color as a CSS hex string.
110    fn dark_color(&self) -> &str;
111
112    /// Light module color as a CSS hex string.
113    fn light_color(&self) -> &str;
114
115    /// Optional module dimensions `(width, height)`.
116    fn module_size(&self) -> Option<(u32, u32)>;
117
118    /// Whether to include the quiet zone.
119    fn quiet_zone(&self) -> bool;
120}
121
122/// Rendering canvas of a QR code image.
123pub trait Canvas: Sized {
124    /// The pixel type stored in this canvas.
125    type Pixel: Sized;
126    /// The finalized image type produced from this canvas.
127    type Image: Sized;
128
129    /// Constructs a new canvas of the given dimensions.
130    fn new(width: u32, height: u32, dark_pixel: Self::Pixel, light_pixel: Self::Pixel) -> Self;
131
132    /// Checks dimensions and pixel-dependent resource limits before allocation.
133    ///
134    /// The default accepts all dimensions, preserving existing custom and
135    /// vector canvases. Buffered backends override this to reject requests that
136    /// exceed their allocation budget. [`Renderer::try_build`] calls this
137    /// before [`Canvas::new`].
138    ///
139    /// # Errors
140    ///
141    /// Returns [`RenderError::OutputTooLarge`] when the backend cannot safely
142    /// represent or allocate the requested output within its resource limits.
143    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    /// Draws a single dark pixel at the (x, y) coordinate.
153    fn draw_dark_pixel(&mut self, x: u32, y: u32);
154
155    /// Draws a filled dark rectangle covering the given module range. Default
156    /// implementation fills it pixel by pixel; override for a faster path.
157    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    /// Finalize the canvas to a real image.
166    fn into_image(self) -> Self::Image;
167}
168
169//}}}
170//------------------------------------------------------------------------------
171//{{{ Renderer
172
173/// Errors returned by fallible render construction or rendering.
174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
175pub enum RenderError {
176    /// The module source is not a non-empty square row-major QR module grid.
177    InvalidModuleSource {
178        /// Source width in modules.
179        width: usize,
180        /// Source height in modules.
181        height: usize,
182        /// Number of row-major modules exposed by the source.
183        len: usize,
184    },
185
186    /// The module source is wider than this renderer can represent internally.
187    ModuleSourceTooWide {
188        /// Source width in modules.
189        width: usize,
190    },
191
192    /// The requested quiet zone, module size, or final canvas dimensions
193    /// overflow this renderer's coordinate space or exceed the backend's
194    /// buffer and output budget.
195    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
213/// A QR code renderer. This is a builder type which converts a bool-vector into
214/// an image.
215pub struct Renderer<'a, P: Pixel> {
216    content: &'a [Color],
217    modules_count: u32, // <- we call it `modules_count` here to avoid ambiguity of `width`.
218    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    /// Creates a new renderer.
228    ///
229    /// # Panics
230    /// panics if content is not `modules_count` squared big
231    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    /// Creates a new renderer from a module-grid source.
245    ///
246    /// This is the read-only-source counterpart to [`Renderer::new`]. It is
247    /// useful when rendering a borrowed view that implements
248    /// [`qrcode_core::ModuleSource`] but does not expose facade-specific QR code
249    /// methods.
250    ///
251    /// # Panics
252    ///
253    /// Panics if `source` is not square or if its row-major module slice length
254    /// does not match `width() * height()`.
255    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    /// Creates a new renderer from a QR symbol.
266    ///
267    /// This is the metadata-aware counterpart to [`Renderer::from_source`].
268    /// The quiet zone is inferred from [`qrcode_core::QrSymbol::quiet_zone`].
269    ///
270    /// # Panics
271    ///
272    /// Panics if `symbol` exposes an invalid module grid.
273    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    /// Tries to create a new renderer from a module-grid source.
281    ///
282    /// Unlike [`Renderer::from_source`], this constructor reports malformed
283    /// sources as [`RenderError`] instead of panicking.
284    ///
285    /// # Errors
286    ///
287    /// Returns [`RenderError::InvalidModuleSource`] when `source` is empty,
288    /// non-square, or its row-major module slice length does not match
289    /// `width() * height()`. Returns [`RenderError::ModuleSourceTooWide`] when
290    /// the width cannot be represented by this renderer.
291    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    /// Tries to create a new renderer from a QR symbol.
311    ///
312    /// This is the fallible, metadata-aware counterpart to
313    /// [`Renderer::try_from_source`]. The quiet zone is inferred from
314    /// [`qrcode_core::QrSymbol::quiet_zone`].
315    ///
316    /// # Errors
317    ///
318    /// Returns the same errors as [`Renderer::try_from_source`] when the symbol
319    /// exposes an invalid module grid.
320    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    /// Sets color of a dark module. Default is opaque black.
328    pub fn dark_color(&mut self, color: P) -> &mut Self {
329        self.dark_color = color;
330        self
331    }
332
333    /// Sets color of a light module. Default is opaque white.
334    pub fn light_color(&mut self, color: P) -> &mut Self {
335        self.light_color = color;
336        self
337    }
338
339    /// Whether to include the quiet zone in the generated image.
340    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    /// Sets the size of each module in pixels. Default is 8×8.
346    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    /// Sets the minimum total image size in pixels, including the quiet zone if
352    /// applicable. The renderer will try to find the dimension as small as
353    /// possible, such that each module in the QR code has uniform size (no
354    /// distortion).
355    ///
356    /// For instance, a version 1 QR code has 19 modules across including the
357    /// quiet zone. If we request an image of size ≥200×200, we get that each
358    /// module's size should be 11×11, so the actual image size will be 209×209.
359    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    /// Sets the maximum total image size in pixels, including the quiet zone if
368    /// applicable. The renderer will try to find the dimension as large as
369    /// possible, such that each module in the QR code has uniform size (no
370    /// distortion).
371    ///
372    /// For instance, a version 1 QR code has 19 modules across including the
373    /// quiet zone. If we request an image of size ≤200×200, we get that each
374    /// module's size should be 10×10, so the actual image size will be 190×190.
375    ///
376    /// The module size is at least 1×1, so if the restriction is too small, the
377    /// final image *can* be larger than the input.
378    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    /// Sets dimensions suitable for web display (200×200 pixels minimum).
387    ///
388    /// This is a convenience preset for embedding QR codes in web pages.
389    /// The actual size may be slightly larger to maintain uniform module sizing.
390    pub fn for_web(&mut self) -> &mut Self {
391        self.min_dimensions(200, 200)
392    }
393
394    /// Sets dimensions suitable for printing at the specified DPI.
395    ///
396    /// Targets a 1-inch × 1-inch physical size. For example, at 300 DPI
397    /// the image will be at least 300×300 pixels.
398    ///
399    /// # Arguments
400    ///
401    /// * `dpi` - Dots per inch (common values: 150 for draft, 300 for standard, 600 for high quality)
402    pub fn for_print(&mut self, dpi: u32) -> &mut Self {
403        self.min_dimensions(dpi.max(72), dpi.max(72))
404    }
405
406    /// Sets dimensions suitable for social media platform sharing.
407    ///
408    /// Targets platform-recommended sizes:
409    ///
410    /// | Platform       | Size (px) |
411    /// |----------------|-----------|
412    /// | `"twitter"`    | 400×400   |
413    /// | `"facebook"`   | 600×600   |
414    /// | `"instagram"`  | 1080×1080 |
415    /// | `"wechat"`     | 600×600   |
416    /// | Any other      | 400×400   |
417    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    /// Tries to render the QR code into an image.
429    ///
430    /// # Errors
431    ///
432    /// Returns [`RenderError::InvalidModuleSource`] if the module grid is empty.
433    /// Returns [`RenderError::OutputTooLarge`] if the configured quiet zone or
434    /// module size would overflow the renderer's coordinate space, or the
435    /// backend rejects the output's estimated buffer and output size.
436    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    /// Renders the QR code into an image.
464    ///
465    /// # Panics
466    ///
467    /// Panics if the module grid is empty, or if the configured quiet zone or
468    /// module size would overflow the renderer's coordinate space, or the
469    /// backend's allocation budget would be exceeded.
470    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    /// Applies a render template: dark/light colors (via
504    /// [`StyledPixel::from_hex`]), optional module size, and the quiet-zone
505    /// setting.
506    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//}}}
518
519#[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}