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
157impl fmt::Display for RenderError {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        match self {
160            RenderError::InvalidModuleSource { width, height, len } => {
161                write!(f, "invalid module source dimensions: width={width}, height={height}, len={len}")
162            }
163            RenderError::ModuleSourceTooWide { width } => write!(f, "module source width {width} exceeds u32::MAX"),
164        }
165    }
166}
167
168#[cfg(feature = "std")]
169impl std::error::Error for RenderError {}
170
171/// A QR code renderer. This is a builder type which converts a bool-vector into
172/// an image.
173pub struct Renderer<'a, P: Pixel> {
174    content: &'a [Color],
175    modules_count: u32, // <- we call it `modules_count` here to avoid ambiguity of `width`.
176    quiet_zone: u32,
177    module_size: (u32, u32),
178
179    dark_color: P,
180    light_color: P,
181    has_quiet_zone: bool,
182}
183
184impl<'a, P: Pixel> Renderer<'a, P> {
185    /// Creates a new renderer.
186    ///
187    /// # Panics
188    /// panics if content is not `modules_count` squared big
189    pub fn new(content: &'a [Color], modules_count: usize, quiet_zone: u32) -> Renderer<'a, P> {
190        assert_eq!(modules_count * modules_count, content.len());
191        Renderer {
192            content,
193            modules_count: modules_count.as_u32(),
194            quiet_zone,
195            module_size: P::default_unit_size(),
196            dark_color: P::default_color(Color::Dark),
197            light_color: P::default_color(Color::Light),
198            has_quiet_zone: true,
199        }
200    }
201
202    /// Creates a new renderer from a module-grid source.
203    ///
204    /// This is the read-only-source counterpart to [`Renderer::new`]. It is
205    /// useful when rendering a borrowed view that implements
206    /// [`qrcode_core::ModuleSource`] but does not expose facade-specific QR code
207    /// methods.
208    ///
209    /// # Panics
210    ///
211    /// Panics if `source` is not square or if its row-major module slice length
212    /// does not match `width() * height()`.
213    pub fn from_source<C>(source: &'a C, quiet_zone: u32) -> Renderer<'a, P>
214    where
215        C: qrcode_core::ModuleSource + ?Sized,
216    {
217        match Self::try_from_source(source, quiet_zone) {
218            Ok(renderer) => renderer,
219            Err(err) => panic!("{err}"),
220        }
221    }
222
223    /// Creates a new renderer from a QR symbol.
224    ///
225    /// This is the metadata-aware counterpart to [`Renderer::from_source`].
226    /// The quiet zone is inferred from [`qrcode_core::QrSymbol::quiet_zone`].
227    ///
228    /// # Panics
229    ///
230    /// Panics if `symbol` exposes an invalid module grid.
231    pub fn from_symbol<S>(symbol: &'a S) -> Renderer<'a, P>
232    where
233        S: qrcode_core::QrSymbol + ?Sized,
234    {
235        Self::from_source(symbol, symbol.quiet_zone())
236    }
237
238    /// Tries to create a new renderer from a module-grid source.
239    ///
240    /// Unlike [`Renderer::from_source`], this constructor reports malformed
241    /// sources as [`RenderError`] instead of panicking.
242    ///
243    /// # Errors
244    ///
245    /// Returns [`RenderError::InvalidModuleSource`] when `source` is empty,
246    /// non-square, or its row-major module slice length does not match
247    /// `width() * height()`. Returns [`RenderError::ModuleSourceTooWide`] when
248    /// the width cannot be represented by this renderer.
249    pub fn try_from_source<C>(source: &'a C, quiet_zone: u32) -> Result<Renderer<'a, P>, RenderError>
250    where
251        C: qrcode_core::ModuleSource + ?Sized,
252    {
253        let width = source.width();
254        let height = source.height();
255        let len = source.modules().len();
256        let Some(expected_len) = width.checked_mul(height) else {
257            return Err(RenderError::InvalidModuleSource { width, height, len });
258        };
259        if width == 0 || width != height || len != expected_len {
260            return Err(RenderError::InvalidModuleSource { width, height, len });
261        }
262        if width > u32::MAX as usize {
263            return Err(RenderError::ModuleSourceTooWide { width });
264        }
265        Ok(Self::new(source.modules(), width, quiet_zone))
266    }
267
268    /// Tries to create a new renderer from a QR symbol.
269    ///
270    /// This is the fallible, metadata-aware counterpart to
271    /// [`Renderer::try_from_source`]. The quiet zone is inferred from
272    /// [`qrcode_core::QrSymbol::quiet_zone`].
273    ///
274    /// # Errors
275    ///
276    /// Returns the same errors as [`Renderer::try_from_source`] when the symbol
277    /// exposes an invalid module grid.
278    pub fn try_from_symbol<S>(symbol: &'a S) -> Result<Renderer<'a, P>, RenderError>
279    where
280        S: qrcode_core::QrSymbol + ?Sized,
281    {
282        Self::try_from_source(symbol, symbol.quiet_zone())
283    }
284
285    /// Sets color of a dark module. Default is opaque black.
286    pub fn dark_color(&mut self, color: P) -> &mut Self {
287        self.dark_color = color;
288        self
289    }
290
291    /// Sets color of a light module. Default is opaque white.
292    pub fn light_color(&mut self, color: P) -> &mut Self {
293        self.light_color = color;
294        self
295    }
296
297    /// Whether to include the quiet zone in the generated image.
298    pub fn quiet_zone(&mut self, has_quiet_zone: bool) -> &mut Self {
299        self.has_quiet_zone = has_quiet_zone;
300        self
301    }
302
303    /// Sets the size of each module in pixels. Default is 8×8.
304    pub fn module_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
305        self.module_size = (max(width, 1), max(height, 1));
306        self
307    }
308
309    /// Sets the minimum total image size in pixels, including the quiet zone if
310    /// applicable. The renderer will try to find the dimension as small as
311    /// possible, such that each module in the QR code has uniform size (no
312    /// distortion).
313    ///
314    /// For instance, a version 1 QR code has 19 modules across including the
315    /// quiet zone. If we request an image of size ≥200×200, we get that each
316    /// module's size should be 11×11, so the actual image size will be 209×209.
317    pub fn min_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
318        let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
319        let width_in_modules = self.modules_count + quiet_zone;
320        let unit_width = width.div_ceil(width_in_modules);
321        let unit_height = height.div_ceil(width_in_modules);
322        self.module_dimensions(unit_width, unit_height)
323    }
324
325    /// Sets the maximum total image size in pixels, including the quiet zone if
326    /// applicable. The renderer will try to find the dimension as large as
327    /// possible, such that each module in the QR code has uniform size (no
328    /// distortion).
329    ///
330    /// For instance, a version 1 QR code has 19 modules across including the
331    /// quiet zone. If we request an image of size ≤200×200, we get that each
332    /// module's size should be 10×10, so the actual image size will be 190×190.
333    ///
334    /// The module size is at least 1×1, so if the restriction is too small, the
335    /// final image *can* be larger than the input.
336    pub fn max_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
337        let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
338        let width_in_modules = self.modules_count + quiet_zone;
339        let unit_width = width / width_in_modules;
340        let unit_height = height / width_in_modules;
341        self.module_dimensions(unit_width, unit_height)
342    }
343
344    /// Sets dimensions suitable for web display (200×200 pixels minimum).
345    ///
346    /// This is a convenience preset for embedding QR codes in web pages.
347    /// The actual size may be slightly larger to maintain uniform module sizing.
348    pub fn for_web(&mut self) -> &mut Self {
349        self.min_dimensions(200, 200)
350    }
351
352    /// Sets dimensions suitable for printing at the specified DPI.
353    ///
354    /// Targets a 1-inch × 1-inch physical size. For example, at 300 DPI
355    /// the image will be at least 300×300 pixels.
356    ///
357    /// # Arguments
358    ///
359    /// * `dpi` - Dots per inch (common values: 150 for draft, 300 for standard, 600 for high quality)
360    pub fn for_print(&mut self, dpi: u32) -> &mut Self {
361        self.min_dimensions(dpi.max(72), dpi.max(72))
362    }
363
364    /// Sets dimensions suitable for social media platform sharing.
365    ///
366    /// Targets platform-recommended sizes:
367    ///
368    /// | Platform       | Size (px) |
369    /// |----------------|-----------|
370    /// | `"twitter"`    | 400×400   |
371    /// | `"facebook"`   | 600×600   |
372    /// | `"instagram"`  | 1080×1080 |
373    /// | `"wechat"`     | 600×600   |
374    /// | Any other      | 400×400   |
375    pub fn for_social(&mut self, platform: &str) -> &mut Self {
376        let size = match platform {
377            "twitter" | "x" => 400,
378            "facebook" | "fb" => 600,
379            "instagram" | "ig" => 1080,
380            "wechat" | "weixin" => 600,
381            _ => 400,
382        };
383        self.min_dimensions(size, size)
384    }
385
386    /// Renders the QR code into an image.
387    pub fn build(&self) -> P::Image {
388        let w = self.modules_count;
389        let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 };
390        let width = w + 2 * qz;
391
392        let (mw, mh) = self.module_size;
393        let real_width = width * mw;
394        let real_height = width * mh;
395
396        let mut canvas = P::Canvas::new(real_width, real_height, self.dark_color, self.light_color);
397        let mut i = 0;
398        for y in 0..width {
399            for x in 0..width {
400                if qz <= x && x < w + qz && qz <= y && y < w + qz {
401                    if self.content[i] != Color::Light {
402                        canvas.draw_dark_rect(x * mw, y * mh, mw, mh);
403                    }
404                    i += 1;
405                }
406            }
407        }
408
409        canvas.into_image()
410    }
411}
412
413impl<C, P> qrcode_core::Renderer<C> for Renderer<'_, P>
414where
415    C: qrcode_core::ModuleSource + ?Sized,
416    P: Pixel,
417{
418    type Output = P::Image;
419    type Error = RenderError;
420
421    fn render(&self, code: &C) -> Result<Self::Output, Self::Error> {
422        let mut renderer = Renderer::try_from_source(code, self.quiet_zone)?;
423        renderer.module_size = self.module_size;
424        renderer.dark_color = self.dark_color;
425        renderer.light_color = self.light_color;
426        renderer.has_quiet_zone = self.has_quiet_zone;
427        Ok(renderer.build())
428    }
429}
430
431impl<'a, P: Pixel> qrcode_core::Builder for &'a Renderer<'a, P> {
432    type Output = P::Image;
433    type Error = RenderError;
434
435    fn build(self) -> Result<Self::Output, Self::Error> {
436        // `Renderer::new` and `try_from_source` establish the square-grid
437        // invariant, while the builder methods only update rendering options.
438        // Keep the fallible trait contract aligned with the renderer trait and
439        // return the existing concrete error type for API consistency.
440        Ok(Renderer::build(self))
441    }
442}
443
444impl<'a, P: StyledPixel> Renderer<'a, P> {
445    /// Applies a render template: dark/light colors (via
446    /// [`StyledPixel::from_hex`]), optional module size, and the quiet-zone
447    /// setting.
448    pub fn template<T: RenderTemplate>(mut self, tmpl: &T) -> Self {
449        self.dark_color = P::from_hex(tmpl.dark_color());
450        self.light_color = P::from_hex(tmpl.light_color());
451        if let Some((w, h)) = tmpl.module_size() {
452            self.module_size = (w, h);
453        }
454        self.has_quiet_zone = tmpl.quiet_zone();
455        self
456    }
457}
458
459//}}}
460
461#[cfg(test)]
462mod tests {
463    use super::{RenderError, Renderer};
464    use qrcode_core::{Color, EcLevel, ModuleSource, QrSymbol, Renderer as CoreRenderer, Version};
465
466    struct BadSource {
467        modules: [Color; 4],
468    }
469
470    impl ModuleSource for BadSource {
471        fn get(&self, x: usize, y: usize) -> Color {
472            self.modules[y * self.width() + x]
473        }
474
475        fn width(&self) -> usize {
476            3
477        }
478
479        fn height(&self) -> usize {
480            2
481        }
482
483        fn modules(&self) -> &[Color] {
484            &self.modules
485        }
486    }
487
488    struct SymbolSource {
489        version: Version,
490        modules: [Color; 1],
491    }
492
493    impl ModuleSource for SymbolSource {
494        fn get(&self, _x: usize, _y: usize) -> Color {
495            self.modules[0]
496        }
497
498        fn width(&self) -> usize {
499            1
500        }
501
502        fn height(&self) -> usize {
503            1
504        }
505
506        fn modules(&self) -> &[Color] {
507            &self.modules
508        }
509    }
510
511    impl QrSymbol for SymbolSource {
512        fn version(&self) -> Version {
513            self.version
514        }
515
516        fn error_correction_level(&self) -> EcLevel {
517            EcLevel::M
518        }
519    }
520
521    #[test]
522    fn try_from_source_returns_error_for_invalid_dimensions() {
523        let source = BadSource { modules: [Color::Dark; 4] };
524
525        let result = Renderer::<char>::try_from_source(&source, 0);
526        assert!(matches!(result, Err(RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 })));
527    }
528
529    #[test]
530    fn core_renderer_returns_error_for_invalid_source() {
531        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
532        let renderer = Renderer::<char>::new(&modules, 2, 0);
533        let source = BadSource { modules: [Color::Dark; 4] };
534
535        assert_eq!(
536            CoreRenderer::render(&renderer, &source).unwrap_err(),
537            RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 }
538        );
539    }
540
541    #[test]
542    fn core_renderer_matches_direct_builder_output() {
543        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
544        let source = qrcode_core::ModuleView::new(&modules, 2).unwrap();
545        let mut renderer = Renderer::<char>::new(&modules, 2, 1);
546        renderer.dark_color('#').light_color('.');
547
548        assert_eq!(CoreRenderer::render(&renderer, &source).unwrap(), renderer.build());
549    }
550
551    #[test]
552    fn core_builder_returns_the_same_output_as_inherent_builder() {
553        let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
554        let mut renderer = Renderer::<char>::new(&modules, 2, 1);
555        renderer.dark_color('#').light_color('.').module_dimensions(2, 3);
556        let expected = renderer.build();
557
558        assert_eq!(qrcode_core::Builder::build(&renderer), Ok(expected));
559    }
560
561    #[test]
562    fn from_symbol_uses_normal_qr_quiet_zone() {
563        let source = SymbolSource { version: Version::Normal(1), modules: [Color::Dark] };
564
565        let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
566
567        assert_eq!(output.lines().next().map(str::len), Some(9));
568    }
569
570    #[test]
571    fn from_symbol_uses_micro_qr_quiet_zone() {
572        let source = SymbolSource { version: Version::Micro(1), modules: [Color::Dark] };
573
574        let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
575
576        assert_eq!(output.lines().next().map(str::len), Some(5));
577    }
578}