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//------------------------------------------------------------------------------
53//{{{ Pixel trait
54
55/// Abstraction of an image pixel.
56pub trait Pixel: Copy + Sized {
57    /// Type of the finalized image.
58    type Image: Sized + 'static;
59
60    /// The type that stores an intermediate buffer before finalizing to a
61    /// concrete image
62    type Canvas: Canvas<Pixel = Self, Image = Self::Image>;
63
64    /// Obtains the default module size. The result must be at least 1×1.
65    fn default_unit_size() -> (u32, u32) {
66        (8, 8)
67    }
68
69    /// Obtains the default pixel color when a module is dark or light.
70    fn default_color(color: Color) -> Self;
71}
72
73/// A [`Pixel`] constructible from a CSS-style hex color string (`"#rrggbb"` or
74/// `"#rgb"`), used by `Renderer::template` to apply a `QrTemplate`.
75///
76/// Implemented for the owned, styled backends (image RGB/RGBA, EPS, PDF, ANSI).
77/// The borrowing backends (`svg::Color`, `html::Color`) are not `StyledPixel`
78/// because their color borrows from the input and can't be stored generically;
79/// apply those colors manually instead.
80pub trait StyledPixel: Pixel {
81    /// Builds a pixel from a hex color string, falling back to black on an
82    /// unparseable value.
83    fn from_hex(hex: &str) -> Self;
84}
85
86/// Styling data that can be applied to a [`Renderer`] with
87/// [`Renderer::template`].
88///
89/// The facade crate implements this for its `QrTemplate`, while downstream
90/// crates can provide their own lightweight template types without depending on
91/// the facade.
92pub trait RenderTemplate {
93    /// Dark module color as a CSS hex string.
94    fn dark_color(&self) -> &str;
95
96    /// Light module color as a CSS hex string.
97    fn light_color(&self) -> &str;
98
99    /// Optional module dimensions `(width, height)`.
100    fn module_size(&self) -> Option<(u32, u32)>;
101
102    /// Whether to include the quiet zone.
103    fn quiet_zone(&self) -> bool;
104}
105
106/// Rendering canvas of a QR code image.
107pub trait Canvas: Sized {
108    /// The pixel type stored in this canvas.
109    type Pixel: Sized;
110    /// The finalized image type produced from this canvas.
111    type Image: Sized;
112
113    /// Constructs a new canvas of the given dimensions.
114    fn new(width: u32, height: u32, dark_pixel: Self::Pixel, light_pixel: Self::Pixel) -> Self;
115
116    /// Draws a single dark pixel at the (x, y) coordinate.
117    fn draw_dark_pixel(&mut self, x: u32, y: u32);
118
119    /// Draws a filled dark rectangle covering the given module range. Default
120    /// implementation fills it pixel by pixel; override for a faster path.
121    fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) {
122        for y in top..(top + height) {
123            for x in left..(left + width) {
124                self.draw_dark_pixel(x, y);
125            }
126        }
127    }
128
129    /// Finalize the canvas to a real image.
130    fn into_image(self) -> Self::Image;
131}
132
133//}}}
134//------------------------------------------------------------------------------
135//{{{ Renderer
136
137/// Errors returned by fallible render construction or rendering.
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
139pub enum RenderError {
140    /// The module source is not a non-empty square row-major QR module grid.
141    InvalidModuleSource {
142        /// Source width in modules.
143        width: usize,
144        /// Source height in modules.
145        height: usize,
146        /// Number of row-major modules exposed by the source.
147        len: usize,
148    },
149
150    /// The module source is wider than this renderer can represent internally.
151    ModuleSourceTooWide {
152        /// Source width in modules.
153        width: usize,
154    },
155
156    /// The requested quiet zone, module size, or final canvas dimensions
157    /// overflow this renderer's `u32` coordinate space.
158    OutputTooLarge,
159}
160
161impl fmt::Display for RenderError {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        match self {
164            RenderError::InvalidModuleSource { width, height, len } => {
165                write!(f, "invalid module source dimensions: width={width}, height={height}, len={len}")
166            }
167            RenderError::ModuleSourceTooWide { width } => write!(f, "module source width {width} exceeds u32::MAX"),
168            RenderError::OutputTooLarge => f.write_str("rendered output dimensions exceed u32::MAX"),
169        }
170    }
171}
172
173#[cfg(feature = "std")]
174impl std::error::Error for RenderError {}
175
176/// A QR code renderer. This is a builder type which converts a bool-vector into
177/// an image.
178pub struct Renderer<'a, P: Pixel> {
179    content: &'a [Color],
180    modules_count: u32, // <- we call it `modules_count` here to avoid ambiguity of `width`.
181    quiet_zone: u32,
182    module_size: (u32, u32),
183
184    dark_color: P,
185    light_color: P,
186    has_quiet_zone: bool,
187}
188
189impl<'a, P: Pixel> Renderer<'a, P> {
190    /// Creates a new renderer.
191    ///
192    /// # Panics
193    /// panics if content is not `modules_count` squared big
194    pub fn new(content: &'a [Color], modules_count: usize, quiet_zone: u32) -> Renderer<'a, P> {
195        assert_eq!(modules_count * modules_count, content.len());
196        Renderer {
197            content,
198            modules_count: modules_count.as_u32(),
199            quiet_zone,
200            module_size: P::default_unit_size(),
201            dark_color: P::default_color(Color::Dark),
202            light_color: P::default_color(Color::Light),
203            has_quiet_zone: true,
204        }
205    }
206
207    /// Creates a new renderer from a module-grid source.
208    ///
209    /// This is the read-only-source counterpart to [`Renderer::new`]. It is
210    /// useful when rendering a borrowed view that implements
211    /// [`qrcode_core::ModuleSource`] but does not expose facade-specific QR code
212    /// methods.
213    ///
214    /// # Panics
215    ///
216    /// Panics if `source` is not square or if its row-major module slice length
217    /// does not match `width() * height()`.
218    pub fn from_source<C>(source: &'a C, quiet_zone: u32) -> Renderer<'a, P>
219    where
220        C: qrcode_core::ModuleSource + ?Sized,
221    {
222        match Self::try_from_source(source, quiet_zone) {
223            Ok(renderer) => renderer,
224            Err(err) => panic!("{err}"),
225        }
226    }
227
228    /// Creates a new renderer from a QR symbol.
229    ///
230    /// This is the metadata-aware counterpart to [`Renderer::from_source`].
231    /// The quiet zone is inferred from [`qrcode_core::QrSymbol::quiet_zone`].
232    ///
233    /// # Panics
234    ///
235    /// Panics if `symbol` exposes an invalid module grid.
236    pub fn from_symbol<S>(symbol: &'a S) -> Renderer<'a, P>
237    where
238        S: qrcode_core::QrSymbol + ?Sized,
239    {
240        Self::from_source(symbol, symbol.quiet_zone())
241    }
242
243    /// Tries to create a new renderer from a module-grid source.
244    ///
245    /// Unlike [`Renderer::from_source`], this constructor reports malformed
246    /// sources as [`RenderError`] instead of panicking.
247    ///
248    /// # Errors
249    ///
250    /// Returns [`RenderError::InvalidModuleSource`] when `source` is empty,
251    /// non-square, or its row-major module slice length does not match
252    /// `width() * height()`. Returns [`RenderError::ModuleSourceTooWide`] when
253    /// the width cannot be represented by this renderer.
254    pub fn try_from_source<C>(source: &'a C, quiet_zone: u32) -> Result<Renderer<'a, P>, RenderError>
255    where
256        C: qrcode_core::ModuleSource + ?Sized,
257    {
258        let width = source.width();
259        let height = source.height();
260        let len = source.modules().len();
261        let Some(expected_len) = width.checked_mul(height) else {
262            return Err(RenderError::InvalidModuleSource { width, height, len });
263        };
264        if width == 0 || width != height || len != expected_len {
265            return Err(RenderError::InvalidModuleSource { width, height, len });
266        }
267        if width > u32::MAX as usize {
268            return Err(RenderError::ModuleSourceTooWide { width });
269        }
270        Ok(Self::new(source.modules(), width, quiet_zone))
271    }
272
273    /// Tries to create a new renderer from a QR symbol.
274    ///
275    /// This is the fallible, metadata-aware counterpart to
276    /// [`Renderer::try_from_source`]. The quiet zone is inferred from
277    /// [`qrcode_core::QrSymbol::quiet_zone`].
278    ///
279    /// # Errors
280    ///
281    /// Returns the same errors as [`Renderer::try_from_source`] when the symbol
282    /// exposes an invalid module grid.
283    pub fn try_from_symbol<S>(symbol: &'a S) -> Result<Renderer<'a, P>, RenderError>
284    where
285        S: qrcode_core::QrSymbol + ?Sized,
286    {
287        Self::try_from_source(symbol, symbol.quiet_zone())
288    }
289
290    /// Sets color of a dark module. Default is opaque black.
291    pub fn dark_color(&mut self, color: P) -> &mut Self {
292        self.dark_color = color;
293        self
294    }
295
296    /// Sets color of a light module. Default is opaque white.
297    pub fn light_color(&mut self, color: P) -> &mut Self {
298        self.light_color = color;
299        self
300    }
301
302    /// Whether to include the quiet zone in the generated image.
303    pub fn quiet_zone(&mut self, has_quiet_zone: bool) -> &mut Self {
304        self.has_quiet_zone = has_quiet_zone;
305        self
306    }
307
308    /// Sets the size of each module in pixels. Default is 8×8.
309    pub fn module_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
310        self.module_size = (max(width, 1), max(height, 1));
311        self
312    }
313
314    /// Sets the minimum total image size in pixels, including the quiet zone if
315    /// applicable. The renderer will try to find the dimension as small as
316    /// possible, such that each module in the QR code has uniform size (no
317    /// distortion).
318    ///
319    /// For instance, a version 1 QR code has 19 modules across including the
320    /// quiet zone. If we request an image of size ≥200×200, we get that each
321    /// module's size should be 11×11, so the actual image size will be 209×209.
322    pub fn min_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
323        let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
324        let width_in_modules = self.modules_count + quiet_zone;
325        let unit_width = width.div_ceil(width_in_modules);
326        let unit_height = height.div_ceil(width_in_modules);
327        self.module_dimensions(unit_width, unit_height)
328    }
329
330    /// Sets the maximum total image size in pixels, including the quiet zone if
331    /// applicable. The renderer will try to find the dimension as large as
332    /// possible, such that each module in the QR code has uniform size (no
333    /// distortion).
334    ///
335    /// For instance, a version 1 QR code has 19 modules across including the
336    /// quiet zone. If we request an image of size ≤200×200, we get that each
337    /// module's size should be 10×10, so the actual image size will be 190×190.
338    ///
339    /// The module size is at least 1×1, so if the restriction is too small, the
340    /// final image *can* be larger than the input.
341    pub fn max_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
342        let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
343        let width_in_modules = self.modules_count + quiet_zone;
344        let unit_width = width / width_in_modules;
345        let unit_height = height / width_in_modules;
346        self.module_dimensions(unit_width, unit_height)
347    }
348
349    /// Sets dimensions suitable for web display (200×200 pixels minimum).
350    ///
351    /// This is a convenience preset for embedding QR codes in web pages.
352    /// The actual size may be slightly larger to maintain uniform module sizing.
353    pub fn for_web(&mut self) -> &mut Self {
354        self.min_dimensions(200, 200)
355    }
356
357    /// Sets dimensions suitable for printing at the specified DPI.
358    ///
359    /// Targets a 1-inch × 1-inch physical size. For example, at 300 DPI
360    /// the image will be at least 300×300 pixels.
361    ///
362    /// # Arguments
363    ///
364    /// * `dpi` - Dots per inch (common values: 150 for draft, 300 for standard, 600 for high quality)
365    pub fn for_print(&mut self, dpi: u32) -> &mut Self {
366        self.min_dimensions(dpi.max(72), dpi.max(72))
367    }
368
369    /// Sets dimensions suitable for social media platform sharing.
370    ///
371    /// Targets platform-recommended sizes:
372    ///
373    /// | Platform       | Size (px) |
374    /// |----------------|-----------|
375    /// | `"twitter"`    | 400×400   |
376    /// | `"facebook"`   | 600×600   |
377    /// | `"instagram"`  | 1080×1080 |
378    /// | `"wechat"`     | 600×600   |
379    /// | Any other      | 400×400   |
380    pub fn for_social(&mut self, platform: &str) -> &mut Self {
381        let size = match platform {
382            "twitter" | "x" => 400,
383            "facebook" | "fb" => 600,
384            "instagram" | "ig" => 1080,
385            "wechat" | "weixin" => 600,
386            _ => 400,
387        };
388        self.min_dimensions(size, size)
389    }
390
391    /// Tries to render the QR code into an image.
392    ///
393    /// # Errors
394    ///
395    /// Returns [`RenderError::OutputTooLarge`] if the configured quiet zone or
396    /// module size would overflow the renderer's coordinate space.
397    pub fn try_build(&self) -> Result<P::Image, RenderError> {
398        let w = self.modules_count;
399        let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 };
400        let quiet = qz.checked_mul(2).ok_or(RenderError::OutputTooLarge)?;
401        let width = w.checked_add(quiet).ok_or(RenderError::OutputTooLarge)?;
402
403        let (mw, mh) = self.module_size;
404        let real_width = width.checked_mul(mw).ok_or(RenderError::OutputTooLarge)?;
405        let real_height = width.checked_mul(mh).ok_or(RenderError::OutputTooLarge)?;
406
407        let mut canvas = P::Canvas::new(real_width, real_height, self.dark_color, self.light_color);
408        for (y, row) in self.content.chunks_exact(w as usize).enumerate() {
409            let top = (y as u32 + qz) * mh;
410            for (x, &module) in row.iter().enumerate() {
411                if module != Color::Light {
412                    canvas.draw_dark_rect((x as u32 + qz) * mw, top, mw, mh);
413                }
414            }
415        }
416
417        Ok(canvas.into_image())
418    }
419
420    /// Renders the QR code into an image.
421    ///
422    /// # Panics
423    ///
424    /// Panics if the configured quiet zone or module size would overflow the
425    /// renderer's coordinate space.
426    pub fn build(&self) -> P::Image {
427        self.try_build().unwrap_or_else(|err| panic!("{err}"))
428    }
429}
430
431impl<C, P> qrcode_core::Renderer<C> for Renderer<'_, P>
432where
433    C: qrcode_core::ModuleSource + ?Sized,
434    P: Pixel,
435{
436    type Output = P::Image;
437    type Error = RenderError;
438
439    fn render(&self, code: &C) -> Result<Self::Output, Self::Error> {
440        let mut renderer = Renderer::try_from_source(code, self.quiet_zone)?;
441        renderer.module_size = self.module_size;
442        renderer.dark_color = self.dark_color;
443        renderer.light_color = self.light_color;
444        renderer.has_quiet_zone = self.has_quiet_zone;
445        renderer.try_build()
446    }
447}
448
449impl<'a, P: Pixel> qrcode_core::Builder for &'a Renderer<'a, P> {
450    type Output = P::Image;
451    type Error = RenderError;
452
453    fn build(self) -> Result<Self::Output, Self::Error> {
454        // `Renderer::new` and `try_from_source` establish the square-grid
455        // invariant, while the builder methods only update rendering options.
456        // Keep the fallible trait contract aligned with the renderer trait and
457        // return the existing concrete error type for API consistency.
458        Ok(Renderer::build(self))
459    }
460}
461
462impl<'a, P: StyledPixel> Renderer<'a, P> {
463    /// Applies a render template: dark/light colors (via
464    /// [`StyledPixel::from_hex`]), optional module size, and the quiet-zone
465    /// setting.
466    pub fn template<T: RenderTemplate>(mut self, tmpl: &T) -> Self {
467        self.dark_color = P::from_hex(tmpl.dark_color());
468        self.light_color = P::from_hex(tmpl.light_color());
469        if let Some((w, h)) = tmpl.module_size() {
470            self.module_size = (w, h);
471        }
472        self.has_quiet_zone = tmpl.quiet_zone();
473        self
474    }
475}
476
477//}}}
478
479#[cfg(test)]
480mod tests {
481    use super::{RenderError, Renderer};
482    use qrcode_core::{Color, EcLevel, ModuleSource, QrSymbol, Renderer as CoreRenderer, Version};
483
484    struct BadSource {
485        modules: [Color; 4],
486    }
487
488    impl ModuleSource for BadSource {
489        fn get(&self, x: usize, y: usize) -> Color {
490            self.modules[y * self.width() + x]
491        }
492
493        fn width(&self) -> usize {
494            3
495        }
496
497        fn height(&self) -> usize {
498            2
499        }
500
501        fn modules(&self) -> &[Color] {
502            &self.modules
503        }
504    }
505
506    struct SymbolSource {
507        version: Version,
508        modules: [Color; 1],
509    }
510
511    impl ModuleSource for SymbolSource {
512        fn get(&self, _x: usize, _y: usize) -> Color {
513            self.modules[0]
514        }
515
516        fn width(&self) -> usize {
517            1
518        }
519
520        fn height(&self) -> usize {
521            1
522        }
523
524        fn modules(&self) -> &[Color] {
525            &self.modules
526        }
527    }
528
529    impl QrSymbol for SymbolSource {
530        fn version(&self) -> Version {
531            self.version
532        }
533
534        fn error_correction_level(&self) -> EcLevel {
535            EcLevel::M
536        }
537    }
538
539    #[test]
540    fn try_from_source_returns_error_for_invalid_dimensions() {
541        let source = BadSource { modules: [Color::Dark; 4] };
542
543        let result = Renderer::<char>::try_from_source(&source, 0);
544        assert!(matches!(result, Err(RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 })));
545    }
546
547    #[test]
548    fn core_renderer_returns_error_for_invalid_source() {
549        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
550        let renderer = Renderer::<char>::new(&modules, 2, 0);
551        let source = BadSource { modules: [Color::Dark; 4] };
552
553        assert_eq!(
554            CoreRenderer::render(&renderer, &source).unwrap_err(),
555            RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 }
556        );
557    }
558
559    #[test]
560    fn core_renderer_matches_direct_builder_output() {
561        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
562        let source = qrcode_core::ModuleView::new(&modules, 2).unwrap();
563        let mut renderer = Renderer::<char>::new(&modules, 2, 1);
564        renderer.dark_color('#').light_color('.');
565
566        assert_eq!(CoreRenderer::render(&renderer, &source).unwrap(), renderer.build());
567    }
568
569    #[test]
570    fn core_builder_returns_the_same_output_as_inherent_builder() {
571        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
572        let mut renderer = Renderer::<char>::new(&modules, 2, 1);
573        renderer.dark_color('#').light_color('.').module_dimensions(2, 3);
574        let expected = renderer.build();
575
576        assert_eq!(qrcode_core::Builder::build(&renderer), Ok(expected));
577    }
578
579    #[test]
580    fn try_build_rejects_overflowing_dimensions() {
581        let modules = [Color::Dark];
582        let mut renderer = Renderer::<char>::new(&modules, 1, u32::MAX);
583        renderer.module_dimensions(u32::MAX, 1);
584
585        assert_eq!(renderer.try_build(), Err(RenderError::OutputTooLarge));
586    }
587
588    #[test]
589    fn build_keeps_quiet_zone_while_scanning_only_source_modules() {
590        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
591        let mut renderer = Renderer::<char>::new(&modules, 2, 1);
592        renderer.dark_color('#').light_color('.').module_dimensions(1, 1);
593
594        assert_eq!(renderer.build(), "....\n.#..\n..#.\n....");
595    }
596
597    #[test]
598    fn from_symbol_uses_normal_qr_quiet_zone() {
599        let source = SymbolSource { version: Version::Normal(1), modules: [Color::Dark] };
600
601        let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
602
603        assert_eq!(output.lines().next().map(str::len), Some(9));
604    }
605
606    #[test]
607    fn from_symbol_uses_micro_qr_quiet_zone() {
608        let source = SymbolSource { version: Version::Micro(1), modules: [Color::Dark] };
609
610        let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
611
612        assert_eq!(output.lines().next().map(str::len), Some(5));
613    }
614}