tui_qrcode/
lib.rs

1//! TUI QR Code is a library for rendering QR codes in a terminal using the [Ratatui] crate.
2//!
3//! [![Crate badge]][tui-qrcode]
4//! [![Docs.rs Badge]][API Docs]
5//! [![Deps.rs Badge]][Dependency Status]
6//! [![License Badge]](./LICENSE-MIT)
7//! [![Discord Badge]][Ratatui Discord]
8//!
9//! [GitHub Repository] · [API Docs] · [Examples] · [Changelog] · [Contributing]
10//!
11//! ![Demo](https://vhs.charm.sh/vhs-nUpcmCP1igCcGoJ5iio07.gif)
12//!
13//! # Usage
14//!
15//! Add qrcode and tui-qrcode to your Cargo.toml. You can disable the default features of qrcode as
16//! we don't need the code which renders the QR code to an image.
17//!
18//! ```shell
19//! cargo add qrcode tui-qrcode --no-default-features
20//! ```
21//!
22//! # Example
23//!
24//! This example can be found in the `examples` directory of the repository.
25//!
26//! ```no_run
27//! use qrcode::QrCode;
28//! use ratatui::crossterm::event;
29//! use ratatui::{DefaultTerminal, Frame};
30//! use tui_qrcode::{Colors, QrCodeWidget};
31//!
32//! fn main() -> color_eyre::Result<()> {
33//!     color_eyre::install()?;
34//!     let terminal = ratatui::init();
35//!     let result = run(terminal);
36//!     ratatui::restore();
37//!     result
38//! }
39//!
40//! fn run(mut terminal: DefaultTerminal) -> color_eyre::Result<()> {
41//!     loop {
42//!         terminal.draw(render)?;
43//!         if matches!(event::read()?, event::Event::Key(_)) {
44//!             break Ok(());
45//!         }
46//!     }
47//! }
48//!
49//! fn render(frame: &mut Frame) {
50//!     let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
51//!     let widget = QrCodeWidget::new(qr_code).colors(Colors::Inverted);
52//!     frame.render_widget(widget, frame.area());
53//! }
54//! ```
55//!
56//! Renders the following QR code:
57//!
58//! ```text
59//! █████████████████████████████████
60//! █████████████████████████████████
61//! ████ ▄▄▄▄▄ █▄ ▄▄▄ ████ ▄▄▄▄▄ ████
62//! ████ █   █ █▄▄▄█▀▄██ █ █   █ ████
63//! ████ █▄▄▄█ █▀   ▄▀ ███ █▄▄▄█ ████
64//! ████▄▄▄▄▄▄▄█▄▀▄█ ▀▄▀ █▄▄▄▄▄▄▄████
65//! ████ █▄▀▀▀▄▄▀▄▄  ▄█▀▄█▀ █▀▄▀ ████
66//! ██████▀█  ▄▀▄▄▀▀ ▄ ▄█ ▄▄█ ▄█▄████
67//! ████▄▀▀▀▄▄▄▄▀█▄▄█  ▀ ▀ ▀███▀ ████
68//! ████▄▄ ▀█▄▄▀▄▄ ▄█▀█▄▀█▄▀▀ ▄█▄████
69//! ████▄▄█▄██▄█ ▄▀▄ ▄█  ▄▄▄ ██▄▀████
70//! ████ ▄▄▄▄▄ █▄▄▄▀ ▄ ▀ █▄█ ███ ████
71//! ████ █   █ ██ ███  ▄▄ ▄▄ █▀ ▄████
72//! ████ █▄▄▄█ █▄▀ ▄█▀█▀ ▄█  ▄█▄▄████
73//! ████▄▄▄▄▄▄▄█▄▄█▄▄▄██▄█▄██▄██▄████
74//! █████████████████████████████████
75//! ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
76//! ```
77//!
78//! [Ratatui]: https://crates.io/crates/ratatui
79//! [Crate badge]: https://img.shields.io/crates/v/tui-qrcode.svg?style=for-the-badge
80//! [tui-qrcode]: https://crates.io/crates/tui-qrcode
81//! [Docs.rs Badge]: https://img.shields.io/badge/docs.rs-tui--qrcode-blue?style=for-the-badge
82//! [API Docs]: https://docs.rs/tui-qrcode
83//! [Deps.rs Badge]: https://deps.rs/repo/github/joshka/tui-qrcode/status.svg?style=for-the-badge
84//! [Dependency Status]: https://deps.rs/repo/github/joshka/tui-qrcode
85//! [License Badge]: https://img.shields.io/crates/l/tui-qrcode?style=for-the-badge
86//! [Discord Badge]:
87//!     https://img.shields.io/discord/1070692720437383208?label=ratatui+discord&logo=discord&style=for-the-badge
88//! [Ratatui Discord]: https://discord.gg/pMCEU9hNEj
89//! [GitHub Repository]: https://github.com/joshka/tui-widgets
90//! [Examples]: https://github.com/joshka/tui-widgets/tree/main/tui-qrcode/examples
91//! [Changelog]: https://github.com/joshka/tui-widgets/blob/main/tui-qrcode/CHANGELOG.md
92//! [Contributing]: https://github.com/joshka/tui-widgets/blob/main/CONTRIBUTING.md
93
94use qrcode::render::unicode::Dense1x2;
95use qrcode::QrCode;
96use ratatui_core::buffer::Buffer;
97use ratatui_core::layout::{Rect, Size};
98use ratatui_core::style::{Style, Styled};
99use ratatui_core::text::Text;
100use ratatui_core::widgets::Widget;
101
102/// A [Ratatui](https://crates.io/crates/ratatui) widget that renders a QR code.
103///
104/// This widget can be used to render a QR code in a terminal. It uses the [qrcode] crate to
105/// generate the QR code.
106///
107/// # Examples
108///
109/// ```no_run
110/// use qrcode::QrCode;
111/// use tui_qrcode::QrCodeWidget;
112///
113/// let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
114/// let widget = QrCodeWidget::new(qr_code);
115/// ```
116///
117/// The widget can be customized using the `quiet_zone`, `scaling`, `colors`, and `style` methods.
118/// Additionally, the widget implements the [`Styled`] trait, so all the methods from Ratatui's
119/// [`ratatui_core::style::Stylize`] trait can be used.
120///
121/// ```no_run
122/// use qrcode::QrCode;
123/// use ratatui::style::{Style, Stylize};
124/// use ratatui::Frame;
125/// use tui_qrcode::{Colors, QrCodeWidget, QuietZone, Scaling};
126///
127/// fn render(frame: &mut Frame) {
128///     let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
129///     let widget = QrCodeWidget::new(qr_code)
130///         .quiet_zone(QuietZone::Disabled)
131///         .scaling(Scaling::Max)
132///         .colors(Colors::Inverted)
133///         .red()
134///         .on_light_yellow();
135///     frame.render_widget(widget, frame.area());
136/// }
137/// ```
138pub struct QrCodeWidget {
139    qr_code: QrCode,
140    quiet_zone: QuietZone,
141    scaling: Scaling,
142    colors: Colors,
143    style: Style,
144}
145
146/// The quiet zone (border) of a QR code.
147#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
148pub enum QuietZone {
149    /// The quiet zone is enabled.
150    #[default]
151    Enabled,
152    /// The quiet zone is disabled.
153    Disabled,
154}
155
156#[derive(Debug, Clone, Copy, Eq, PartialEq)]
157pub enum Scaling {
158    /// The QR code will be scaled to at least the size of the widget.
159    ///
160    /// Note that this usually results in a QR code that is larger than the widget, which is not
161    /// ideal.
162    Min,
163
164    /// The QR code will be scaled to be at most the size of the widget.
165    ///
166    /// Note that this may result in a QR code which is scaled more horizontally or vertically than
167    /// the other, which may not be ideal.
168    Max,
169
170    /// The QR code will be scaled so each pixel is the size of the given dimensions.
171    ///
172    /// The minimum dimensions are 1x1 (width x height).
173    Exact(u16, u16),
174}
175
176impl Default for Scaling {
177    fn default() -> Self {
178        Self::Exact(1, 1)
179    }
180}
181
182#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
183pub enum Colors {
184    /// The default colors. (Black on white)
185    #[default]
186    Normal,
187
188    /// The colors are inverted. (White on black)
189    Inverted,
190}
191
192impl QrCodeWidget {
193    /// Create a new QR code widget.
194    #[must_use]
195    pub fn new(qr_code: QrCode) -> Self {
196        Self {
197            qr_code,
198            quiet_zone: QuietZone::default(),
199            scaling: Scaling::default(),
200            colors: Colors::default(),
201            style: Style::default(),
202        }
203    }
204
205    /// Set whether the QR code should have a quiet zone.
206    ///
207    /// This is the white border around the QR code. By default, the quiet zone is enabled.
208    ///
209    /// # Example
210    ///
211    /// ```
212    /// use qrcode::QrCode;
213    /// use tui_qrcode::{QrCodeWidget, QuietZone};
214    ///
215    /// let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
216    /// let widget = QrCodeWidget::new(qr_code).quiet_zone(QuietZone::Disabled);
217    /// ```
218    #[must_use]
219    pub const fn quiet_zone(mut self, quiet_zone: QuietZone) -> Self {
220        self.quiet_zone = quiet_zone;
221        self
222    }
223
224    /// Set how the QR code should be scaled.
225    ///
226    /// By default, the QR code will be scaled so each pixel is 1x1.
227    ///
228    /// The `Min` variant will scale the QR code so it is at least the size of the widget. This may
229    /// result in a QR code that is larger than the widget, which is not ideal. The `Max` variant
230    /// will scale the QR code so it is at most the size of the widget. This may result in a QR code
231    /// which is scaled more horizontally or vertically than the other, which may not be ideal. The
232    /// `Exact` variant will scale the QR code so each pixel is the size of the given dimensions.
233    /// The minimum scaling is 1x1 (width x height).
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// use qrcode::QrCode;
239    /// use tui_qrcode::{QrCodeWidget, Scaling};
240    ///
241    /// let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
242    /// let widget = QrCodeWidget::new(qr_code).scaling(Scaling::Max);
243    /// ```
244    #[must_use]
245    pub const fn scaling(mut self, scaling: Scaling) -> Self {
246        self.scaling = scaling;
247        self
248    }
249
250    /// Set the colors of the QR code.
251    ///
252    /// By default, the colors are normal (light on dark).
253    ///
254    /// The `Normal` variant will use the default colors. The `Inverted` variant will invert the
255    /// colors (dark on light).
256    ///
257    /// To set the foreground and background colors of the widget, use the `style` method.
258    ///
259    /// # Example
260    ///
261    /// ```
262    /// use qrcode::QrCode;
263    /// use tui_qrcode::{Colors, QrCodeWidget};
264    ///
265    /// let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
266    /// let widget = QrCodeWidget::new(qr_code).colors(Colors::Inverted);
267    /// ```
268    #[must_use]
269    pub const fn colors(mut self, colors: Colors) -> Self {
270        self.colors = colors;
271        self
272    }
273
274    /// Set the style of the widget.
275    ///
276    /// This will set the foreground and background colors of the widget.
277    ///
278    /// # Example
279    ///
280    /// ```
281    /// use qrcode::QrCode;
282    /// use ratatui::style::{Style, Stylize};
283    /// use tui_qrcode::QrCodeWidget;
284    ///
285    /// let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
286    /// let style = Style::new().red().on_light_yellow();
287    /// let widget = QrCodeWidget::new(qr_code).style(style);
288    /// ```
289    #[must_use]
290    pub fn style(mut self, style: impl Into<Style>) -> Self {
291        self.style = style.into();
292        self
293    }
294
295    /// The theoretical size of the QR code if rendered into `area`.
296    ///
297    /// Note that if the QR code does not fit into `area`, the resulting [`Size`] might be larger
298    /// than the size of `area`.
299    ///
300    /// # Example
301    /// ```
302    /// use qrcode::QrCode;
303    /// use ratatui::layout::Rect;
304    /// use tui_qrcode::{QrCodeWidget, Scaling};
305    ///
306    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
307    /// let qr_code = QrCode::new("https://ratatui.rs")?;
308    /// let widget = QrCodeWidget::new(qr_code).scaling(Scaling::Min);
309    /// let area = Rect::new(0, 0, 50, 50);
310    /// let widget_size = widget.size(area);
311    ///
312    /// assert_eq!(widget_size.width, 66);
313    /// assert_eq!(widget_size.height, 66);
314    /// # Ok(())
315    /// # }
316    /// ```
317    #[must_use]
318    pub fn size(&self, area: Rect) -> Size {
319        let qr_width: u16 = match self.quiet_zone {
320            QuietZone::Enabled => 8,
321            QuietZone::Disabled => 0,
322        } + self.qr_code.width() as u16;
323
324        let (x, y) = match self.scaling {
325            Scaling::Exact(x, y) => (x, y),
326            Scaling::Min => {
327                let x = area.width.div_ceil(qr_width);
328                let y = (area.height * 2).div_ceil(qr_width);
329                (x, y)
330            }
331            Scaling::Max => {
332                let x = area.width / qr_width;
333                let y = (area.height * 2) / qr_width;
334                (x, y)
335            }
336        };
337        let (x, y) = (x.max(1), y.max(1));
338        let width = qr_width * x;
339        let height = (qr_width * y).div_ceil(2);
340        Size::new(width, height)
341    }
342}
343
344impl Styled for QrCodeWidget {
345    type Item = Self;
346
347    fn style(&self) -> Style {
348        self.style
349    }
350
351    fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
352        self.style(style)
353    }
354}
355
356impl Widget for QrCodeWidget {
357    fn render(self, area: Rect, buf: &mut Buffer) {
358        (&self).render(area, buf);
359    }
360}
361
362impl Widget for &QrCodeWidget {
363    fn render(self, area: Rect, buf: &mut Buffer) {
364        let mut renderer = self.qr_code.render::<Dense1x2>();
365        match self.quiet_zone {
366            QuietZone::Enabled => renderer.quiet_zone(true),
367            QuietZone::Disabled => renderer.quiet_zone(false),
368        };
369        match self.scaling {
370            Scaling::Min => renderer.min_dimensions(area.width as u32, area.height as u32 * 2),
371            Scaling::Max => renderer.max_dimensions(area.width as u32, area.height as u32 * 2),
372            Scaling::Exact(width, height) => {
373                renderer.module_dimensions(width as u32, height as u32)
374            }
375        };
376        match self.colors {
377            Colors::Normal => renderer
378                .dark_color(Dense1x2::Dark)
379                .light_color(Dense1x2::Light),
380            Colors::Inverted => renderer
381                .dark_color(Dense1x2::Light)
382                .light_color(Dense1x2::Dark),
383        };
384        Text::raw(renderer.build())
385            .style(self.style)
386            .render(area, buf);
387    }
388}
389
390#[cfg(test)]
391mod tests {
392    use rstest::{fixture, rstest};
393
394    use super::*;
395
396    /// Creates an empty QR code widget. The basic dimensions of the QR code are 21x21 or 29x29 with
397    /// a quiet zone.
398    #[fixture]
399    fn empty_widget() -> QrCodeWidget {
400        let empty_qr = QrCode::new("").expect("failed to create QR code");
401        QrCodeWidget::new(empty_qr).quiet_zone(QuietZone::Disabled)
402    }
403
404    #[rstest]
405    /// Cases where the QR code is smaller (21x10.5) than the area (22, 12)
406    #[case::smaller_exact((22,12), Scaling::Exact(1, 1), (21, 11))]
407    #[case::smaller_max((22, 12), Scaling::Max, (21, 11))]
408    #[case::smaller_min((22,12),Scaling::Min, (42, 21))]
409    /// Cases where the QR code is the same size (21x10.5) as the area (21, 11)
410    #[case::same_exact((21, 11), Scaling::Exact(1, 1), (21, 11))]
411    #[case::same_max((21, 11), Scaling::Max, (21, 11))]
412    /// Exception: height would be 10.5, so height is doubled to 21
413    #[case::same_min((21, 11), Scaling::Min, (21, 21))]
414    /// Cases where the QR code is larger (21x10.5) than the area (20, 10)
415    #[rstest]
416    #[case::larger_exact((20, 10), Scaling::Exact(1, 1), (21, 11))]
417    #[case::larger_max((20, 10), Scaling::Max, (21, 11))]
418    #[case::larger_min((20, 10), Scaling::Min, (21, 11))]
419    /// Cases where the QR code is much smaller (21x10.5) than the area (71, 71).
420    #[rstest]
421    #[case::huge_exact((71, 71), Scaling::Exact(1, 1), (21, 11))]
422    #[case::huge_max((71, 71), Scaling::Max,(63, 63))]
423    #[case::huge_min((71, 71), Scaling::Min, (84, 74))]
424    fn size(
425        empty_widget: QrCodeWidget,
426        #[case] rect: (u16, u16),
427        #[case] scaling: Scaling,
428        #[case] expected: (u16, u16),
429    ) {
430        let rect = Rect::new(0, 0, rect.0, rect.1);
431        let widget = empty_widget.scaling(scaling);
432        assert_eq!(widget.size(rect), Size::from(expected));
433    }
434
435    /// Testing that a QR code with a quiet zone (29x14.5) is scaled correctly into a large area
436    /// (71x71).
437    #[rstest]
438    #[case::huge_exact(Scaling::Exact(1, 1), (29, 15))]
439    #[case::huge_max(Scaling::Max, (58, 58))]
440    #[case::huge_min(Scaling::Min, (87, 73))]
441    fn size_with_quiet_zone(
442        empty_widget: QrCodeWidget,
443        #[case] scaling: Scaling,
444        #[case] expected: (u16, u16),
445    ) {
446        let rect = Rect::new(0, 0, 71, 71);
447        let widget = empty_widget.quiet_zone(QuietZone::Enabled).scaling(scaling);
448        assert_eq!(widget.size(rect), Size::from(expected));
449    }
450
451    /// The QR code fits into the area without scaling
452    #[rstest]
453    fn render_exact_into_fitting_area(empty_widget: QrCodeWidget) {
454        let mut buf = Buffer::empty(Rect::new(0, 0, 21, 11));
455        empty_widget.render(buf.area, &mut buf);
456        assert_eq!(
457            buf,
458            Buffer::with_lines([
459                "█▀▀▀▀▀█  ▀▀▄█ █▀▀▀▀▀█",
460                "█ ███ █ █▀▀ ▀ █ ███ █",
461                "█ ▀▀▀ █ ██▄▄▀ █ ▀▀▀ █",
462                "▀▀▀▀▀▀▀ █▄▀ ▀ ▀▀▀▀▀▀▀",
463                "▀ ▀█▀█▀▄ ▀██▄▄█▀▀█▀▄ ",
464                "▄▄▄   ▀██▀▄▄█▄█▀ ▄ ▄ ",
465                "▀ ▀ ▀▀▀ █▄█ █  █  █  ",
466                "█▀▀▀▀▀█ ▄██▀ ▀ ▄█▀█▀█",
467                "█ ███ █ █▀██▄█▄ ▀█▀▀▀",
468                "█ ▀▀▀ █ ▀  ▄█▄█▀ ▄   ",
469                "▀▀▀▀▀▀▀ ▀   ▀  ▀  ▀  ",
470            ])
471        );
472    }
473
474    /// The QR code fits into the area without scaling
475    #[rstest]
476    fn render_max_into_fitting_area(empty_widget: QrCodeWidget) {
477        let mut buf = Buffer::empty(Rect::new(0, 0, 21, 11));
478        empty_widget
479            .scaling(Scaling::Max)
480            .render(buf.area, &mut buf);
481        assert_eq!(
482            buf,
483            Buffer::with_lines([
484                "█▀▀▀▀▀█  ▀▀▄█ █▀▀▀▀▀█",
485                "█ ███ █ █▀▀ ▀ █ ███ █",
486                "█ ▀▀▀ █ ██▄▄▀ █ ▀▀▀ █",
487                "▀▀▀▀▀▀▀ █▄▀ ▀ ▀▀▀▀▀▀▀",
488                "▀ ▀█▀█▀▄ ▀██▄▄█▀▀█▀▄ ",
489                "▄▄▄   ▀██▀▄▄█▄█▀ ▄ ▄ ",
490                "▀ ▀ ▀▀▀ █▄█ █  █  █  ",
491                "█▀▀▀▀▀█ ▄██▀ ▀ ▄█▀█▀█",
492                "█ ███ █ █▀██▄█▄ ▀█▀▀▀",
493                "█ ▀▀▀ █ ▀  ▄█▄█▀ ▄   ",
494                "▀▀▀▀▀▀▀ ▀   ▀  ▀  ▀  ",
495            ])
496        );
497    }
498
499    // The QR code is doubled vertically as the min scaling means this needs to render at least
500    // 21x10.5 but the buffer is 21x11
501    ///
502    /// Note: this is an instance where the square aspect ratio of the QR code is not preserved
503    /// correctly. This doesn't align with the documentation of the qrcode crate.
504    #[rstest]
505    fn render_min_into_fitting_area(empty_widget: QrCodeWidget) {
506        let mut buf = Buffer::empty(Rect::new(0, 0, 21, 11));
507        empty_widget
508            .scaling(Scaling::Min)
509            .render(buf.area, &mut buf);
510        assert_eq!(
511            buf,
512            Buffer::with_lines([
513                "███████  ██ █ ███████",
514                "█     █    ██ █     █",
515                "█ ███ █ ███ █ █ ███ █",
516                "█ ███ █ █     █ ███ █",
517                "█ ███ █ ██  █ █ ███ █",
518                "█     █ ████  █     █",
519                "███████ █ █ █ ███████",
520                "        ██           ",
521                "█ █████  ███  █████  ",
522                "   █ █ █  █████  █ █ ",
523                "      ████  █ ██     ",
524            ])
525        );
526    }
527
528    /// The QR code fits into the area without scaling
529    #[rstest]
530    fn render_exact_into_larger_area(empty_widget: QrCodeWidget) {
531        let mut buf = Buffer::empty(Rect::new(0, 0, 22, 12));
532        empty_widget.render(buf.area, &mut buf);
533        assert_eq!(
534            buf,
535            Buffer::with_lines([
536                "█▀▀▀▀▀█  ▀▀▄█ █▀▀▀▀▀█ ",
537                "█ ███ █ █▀▀ ▀ █ ███ █ ",
538                "█ ▀▀▀ █ ██▄▄▀ █ ▀▀▀ █ ",
539                "▀▀▀▀▀▀▀ █▄▀ ▀ ▀▀▀▀▀▀▀ ",
540                "▀ ▀█▀█▀▄ ▀██▄▄█▀▀█▀▄  ",
541                "▄▄▄   ▀██▀▄▄█▄█▀ ▄ ▄  ",
542                "▀ ▀ ▀▀▀ █▄█ █  █  █   ",
543                "█▀▀▀▀▀█ ▄██▀ ▀ ▄█▀█▀█ ",
544                "█ ███ █ █▀██▄█▄ ▀█▀▀▀ ",
545                "█ ▀▀▀ █ ▀  ▄█▄█▀ ▄    ",
546                "▀▀▀▀▀▀▀ ▀   ▀  ▀  ▀   ",
547                "                      ",
548            ])
549        );
550    }
551
552    /// The QR code fits into the area without scaling
553    #[rstest]
554    fn render_max_into_larger_area(empty_widget: QrCodeWidget) {
555        let mut buf = Buffer::empty(Rect::new(0, 0, 22, 12));
556        empty_widget
557            .scaling(Scaling::Max)
558            .render(buf.area, &mut buf);
559        assert_eq!(
560            buf,
561            Buffer::with_lines([
562                "█▀▀▀▀▀█  ▀▀▄█ █▀▀▀▀▀█ ",
563                "█ ███ █ █▀▀ ▀ █ ███ █ ",
564                "█ ▀▀▀ █ ██▄▄▀ █ ▀▀▀ █ ",
565                "▀▀▀▀▀▀▀ █▄▀ ▀ ▀▀▀▀▀▀▀ ",
566                "▀ ▀█▀█▀▄ ▀██▄▄█▀▀█▀▄  ",
567                "▄▄▄   ▀██▀▄▄█▄█▀ ▄ ▄  ",
568                "▀ ▀ ▀▀▀ █▄█ █  █  █   ",
569                "█▀▀▀▀▀█ ▄██▀ ▀ ▄█▀█▀█ ",
570                "█ ███ █ █▀██▄█▄ ▀█▀▀▀ ",
571                "█ ▀▀▀ █ ▀  ▄█▄█▀ ▄    ",
572                "▀▀▀▀▀▀▀ ▀   ▀  ▀  ▀   ",
573                "                      ",
574            ])
575        );
576    }
577
578    /// The QR code is doubled vertically and horizontall as the min scaling means this needs to
579    /// render at least 21x10.5 but the buffer is 22x12
580    #[rstest]
581    fn render_min_into_larger_area(empty_widget: QrCodeWidget) {
582        let mut buf = Buffer::empty(Rect::new(0, 0, 22, 12));
583        empty_widget
584            .scaling(Scaling::Min)
585            .render(buf.area, &mut buf);
586        assert_eq!(
587            buf,
588            Buffer::with_lines([
589                "██████████████    ████",
590                "██          ██        ",
591                "██  ██████  ██  ██████",
592                "██  ██████  ██  ██    ",
593                "██  ██████  ██  ████  ",
594                "██          ██  ██████",
595                "██████████████  ██  ██",
596                "                ████  ",
597                "██  ██████████    ████",
598                "      ██  ██  ██    ██",
599                "            ████████  ",
600                "██████        ████  ██",
601            ])
602        );
603    }
604
605    /// The QR code is truncated as the area is smaller than the QR code
606    #[rstest]
607    fn render_exact_into_smaler_area(empty_widget: QrCodeWidget) {
608        let mut buf = Buffer::empty(Rect::new(0, 0, 20, 10));
609        empty_widget.render(buf.area, &mut buf);
610        assert_eq!(
611            buf,
612            Buffer::with_lines([
613                "█▀▀▀▀▀█  ▀▀▄█ █▀▀▀▀▀",
614                "█ ███ █ █▀▀ ▀ █ ███ ",
615                "█ ▀▀▀ █ ██▄▄▀ █ ▀▀▀ ",
616                "▀▀▀▀▀▀▀ █▄▀ ▀ ▀▀▀▀▀▀",
617                "▀ ▀█▀█▀▄ ▀██▄▄█▀▀█▀▄",
618                "▄▄▄   ▀██▀▄▄█▄█▀ ▄ ▄",
619                "▀ ▀ ▀▀▀ █▄█ █  █  █ ",
620                "█▀▀▀▀▀█ ▄██▀ ▀ ▄█▀█▀",
621                "█ ███ █ █▀██▄█▄ ▀█▀▀",
622                "█ ▀▀▀ █ ▀  ▄█▄█▀ ▄  ",
623            ])
624        );
625    }
626
627    /// The QR code is truncated as the max scaling means this needs to render at most 21x10.5 but
628    /// the buffer is 20x10
629    #[rstest]
630    fn render_max_into_smaller_area(empty_widget: QrCodeWidget) {
631        let mut buf = Buffer::empty(Rect::new(0, 0, 20, 10));
632        empty_widget
633            .scaling(Scaling::Max)
634            .render(buf.area, &mut buf);
635        assert_eq!(
636            buf,
637            Buffer::with_lines([
638                "█▀▀▀▀▀█  ▀▀▄█ █▀▀▀▀▀",
639                "█ ███ █ █▀▀ ▀ █ ███ ",
640                "█ ▀▀▀ █ ██▄▄▀ █ ▀▀▀ ",
641                "▀▀▀▀▀▀▀ █▄▀ ▀ ▀▀▀▀▀▀",
642                "▀ ▀█▀█▀▄ ▀██▄▄█▀▀█▀▄",
643                "▄▄▄   ▀██▀▄▄█▄█▀ ▄ ▄",
644                "▀ ▀ ▀▀▀ █▄█ █  █  █ ",
645                "█▀▀▀▀▀█ ▄██▀ ▀ ▄█▀█▀",
646                "█ ███ █ █▀██▄█▄ ▀█▀▀",
647                "█ ▀▀▀ █ ▀  ▄█▄█▀ ▄  ",
648            ])
649        );
650    }
651
652    /// The QR code is truncated as the min scaling means this needs to render at least 21x10.5 but
653    /// the buffer is already too small
654    #[rstest]
655    fn render_min_into_smaller_area(empty_widget: QrCodeWidget) {
656        let mut buf = Buffer::empty(Rect::new(0, 0, 20, 10));
657        empty_widget
658            .scaling(Scaling::Min)
659            .render(buf.area, &mut buf);
660        assert_eq!(
661            buf,
662            Buffer::with_lines([
663                "█▀▀▀▀▀█  ▀▀▄█ █▀▀▀▀▀",
664                "█ ███ █ █▀▀ ▀ █ ███ ",
665                "█ ▀▀▀ █ ██▄▄▀ █ ▀▀▀ ",
666                "▀▀▀▀▀▀▀ █▄▀ ▀ ▀▀▀▀▀▀",
667                "▀ ▀█▀█▀▄ ▀██▄▄█▀▀█▀▄",
668                "▄▄▄   ▀██▀▄▄█▄█▀ ▄ ▄",
669                "▀ ▀ ▀▀▀ █▄█ █  █  █ ",
670                "█▀▀▀▀▀█ ▄██▀ ▀ ▄█▀█▀",
671                "█ ███ █ █▀██▄█▄ ▀█▀▀",
672                "█ ▀▀▀ █ ▀  ▄█▄█▀ ▄  ",
673            ])
674        );
675    }
676
677    /// Exact scaling doesn't scale the QR code
678    #[rstest]
679    fn render_exact_double_height(empty_widget: QrCodeWidget) {
680        let mut buf = Buffer::empty(Rect::new(0, 0, 21, 21));
681        empty_widget.render(buf.area, &mut buf);
682        assert_eq!(
683            buf,
684            Buffer::with_lines([
685                "█▀▀▀▀▀█  ▀▀▄█ █▀▀▀▀▀█",
686                "█ ███ █ █▀▀ ▀ █ ███ █",
687                "█ ▀▀▀ █ ██▄▄▀ █ ▀▀▀ █",
688                "▀▀▀▀▀▀▀ █▄▀ ▀ ▀▀▀▀▀▀▀",
689                "▀ ▀█▀█▀▄ ▀██▄▄█▀▀█▀▄ ",
690                "▄▄▄   ▀██▀▄▄█▄█▀ ▄ ▄ ",
691                "▀ ▀ ▀▀▀ █▄█ █  █  █  ",
692                "█▀▀▀▀▀█ ▄██▀ ▀ ▄█▀█▀█",
693                "█ ███ █ █▀██▄█▄ ▀█▀▀▀",
694                "█ ▀▀▀ █ ▀  ▄█▄█▀ ▄   ",
695                "▀▀▀▀▀▀▀ ▀   ▀  ▀  ▀  ",
696                "                     ",
697                "                     ",
698                "                     ",
699                "                     ",
700                "                     ",
701                "                     ",
702                "                     ",
703                "                     ",
704                "                     ",
705                "                     ",
706            ])
707        );
708    }
709
710    /// The QR code is doubled vertically
711    ///
712    /// Note: this is an instance where the square aspect ratio of the QR code is not preserved
713    /// correctly. This doesn't align with the documentation of the qrcode crate.
714    #[rstest]
715    fn render_max_double_height(empty_widget: QrCodeWidget) {
716        let mut buf = Buffer::empty(Rect::new(0, 0, 21, 21));
717        empty_widget
718            .scaling(Scaling::Max)
719            .render(buf.area, &mut buf);
720        assert_eq!(
721            buf,
722            Buffer::with_lines([
723                "███████  ██ █ ███████",
724                "█     █    ██ █     █",
725                "█ ███ █ ███ █ █ ███ █",
726                "█ ███ █ █     █ ███ █",
727                "█ ███ █ ██  █ █ ███ █",
728                "█     █ ████  █     █",
729                "███████ █ █ █ ███████",
730                "        ██           ",
731                "█ █████  ███  █████  ",
732                "   █ █ █  █████  █ █ ",
733                "      ████  █ ██     ",
734                "███    ██ █████  █ █ ",
735                "█ █ ███ █ █ █  █  █  ",
736                "        ███ █  █  █  ",
737                "███████  ███ █  █████",
738                "█     █ ███    ██ █ █",
739                "█ ███ █ ████ █  █████",
740                "█ ███ █ █ █████  █   ",
741                "█ ███ █ █   █ ██     ",
742                "█     █    ████  █   ",
743                "███████ █   █  █  █  ",
744            ])
745        );
746    }
747
748    /// The QR code is doubled vertically
749    ///
750    /// Note: this is an instance where the square aspect ratio of the QR code is not preserved
751    /// correctly. This doesn't align with the documentation of the qrcode crate.
752    #[rstest]
753    fn render_min_double_height(empty_widget: QrCodeWidget) {
754        let mut buf = Buffer::empty(Rect::new(0, 0, 21, 21));
755        empty_widget
756            .scaling(Scaling::Min)
757            .render(buf.area, &mut buf);
758        assert_eq!(
759            buf,
760            Buffer::with_lines([
761                "███████  ██ █ ███████",
762                "█     █    ██ █     █",
763                "█ ███ █ ███ █ █ ███ █",
764                "█ ███ █ █     █ ███ █",
765                "█ ███ █ ██  █ █ ███ █",
766                "█     █ ████  █     █",
767                "███████ █ █ █ ███████",
768                "        ██           ",
769                "█ █████  ███  █████  ",
770                "   █ █ █  █████  █ █ ",
771                "      ████  █ ██     ",
772                "███    ██ █████  █ █ ",
773                "█ █ ███ █ █ █  █  █  ",
774                "        ███ █  █  █  ",
775                "███████  ███ █  █████",
776                "█     █ ███    ██ █ █",
777                "█ ███ █ ████ █  █████",
778                "█ ███ █ █ █████  █   ",
779                "█ ███ █ █   █ ██     ",
780                "█     █    ████  █   ",
781                "███████ █   █  █  █  ",
782            ])
783        );
784    }
785
786    #[rstest]
787    fn render_exact_double_width(empty_widget: QrCodeWidget) {
788        let mut buf = Buffer::empty(Rect::new(0, 0, 42, 11));
789        empty_widget.render(buf.area, &mut buf);
790        assert_eq!(
791            buf,
792            Buffer::with_lines([
793                "█▀▀▀▀▀█  ▀▀▄█ █▀▀▀▀▀█                     ",
794                "█ ███ █ █▀▀ ▀ █ ███ █                     ",
795                "█ ▀▀▀ █ ██▄▄▀ █ ▀▀▀ █                     ",
796                "▀▀▀▀▀▀▀ █▄▀ ▀ ▀▀▀▀▀▀▀                     ",
797                "▀ ▀█▀█▀▄ ▀██▄▄█▀▀█▀▄                      ",
798                "▄▄▄   ▀██▀▄▄█▄█▀ ▄ ▄                      ",
799                "▀ ▀ ▀▀▀ █▄█ █  █  █                       ",
800                "█▀▀▀▀▀█ ▄██▀ ▀ ▄█▀█▀█                     ",
801                "█ ███ █ █▀██▄█▄ ▀█▀▀▀                     ",
802                "█ ▀▀▀ █ ▀  ▄█▄█▀ ▄                        ",
803                "▀▀▀▀▀▀▀ ▀   ▀  ▀  ▀                       ",
804            ])
805        );
806    }
807
808    /// The QR code is doubled horizontally as the max scaling means this needs to render at most
809    /// 42x10.5 but the buffer is 42x11
810    ///
811    /// Note: this is an instance where the square aspect ratio of the QR code is not preserved
812    /// correctly. This doesn't align with the documentation of the qrcode crate.
813    #[rstest]
814    fn render_max_double_width(empty_widget: QrCodeWidget) {
815        let mut buf = Buffer::empty(Rect::new(0, 0, 42, 11));
816        empty_widget
817            .scaling(Scaling::Max)
818            .render(buf.area, &mut buf);
819        assert_eq!(
820            buf,
821            Buffer::with_lines([
822                "██▀▀▀▀▀▀▀▀▀▀██    ▀▀▀▀▄▄██  ██▀▀▀▀▀▀▀▀▀▀██",
823                "██  ██████  ██  ██▀▀▀▀  ▀▀  ██  ██████  ██",
824                "██  ▀▀▀▀▀▀  ██  ████▄▄▄▄▀▀  ██  ▀▀▀▀▀▀  ██",
825                "▀▀▀▀▀▀▀▀▀▀▀▀▀▀  ██▄▄▀▀  ▀▀  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
826                "▀▀  ▀▀██▀▀██▀▀▄▄  ▀▀████▄▄▄▄██▀▀▀▀██▀▀▄▄  ",
827                "▄▄▄▄▄▄      ▀▀████▀▀▄▄▄▄██▄▄██▀▀  ▄▄  ▄▄  ",
828                "▀▀  ▀▀  ▀▀▀▀▀▀  ██▄▄██  ██    ██    ██    ",
829                "██▀▀▀▀▀▀▀▀▀▀██  ▄▄████▀▀  ▀▀  ▄▄██▀▀██▀▀██",
830                "██  ██████  ██  ██▀▀████▄▄██▄▄  ▀▀██▀▀▀▀▀▀",
831                "██  ▀▀▀▀▀▀  ██  ▀▀    ▄▄██▄▄██▀▀  ▄▄      ",
832                "▀▀▀▀▀▀▀▀▀▀▀▀▀▀  ▀▀      ▀▀    ▀▀    ▀▀    ",
833            ])
834        );
835    }
836
837    /// Both the width and height are doubled because the min scaling means the QR code needs to be
838    /// at least 42x10.5 but the buffer is 42x11
839    #[rstest]
840    fn render_min_double_width(empty_widget: QrCodeWidget) {
841        let mut buf = Buffer::empty(Rect::new(0, 0, 42, 11));
842        empty_widget
843            .scaling(Scaling::Min)
844            .render(buf.area, &mut buf);
845        assert_eq!(
846            buf,
847            Buffer::with_lines([
848                "██████████████    ████  ██  ██████████████",
849                "██          ██        ████  ██          ██",
850                "██  ██████  ██  ██████  ██  ██  ██████  ██",
851                "██  ██████  ██  ██          ██  ██████  ██",
852                "██  ██████  ██  ████    ██  ██  ██████  ██",
853                "██          ██  ████████    ██          ██",
854                "██████████████  ██  ██  ██  ██████████████",
855                "                ████                      ",
856                "██  ██████████    ██████    ██████████    ",
857                "      ██  ██  ██    ██████████    ██  ██  ",
858                "            ████████    ██  ████          ",
859            ])
860        );
861    }
862
863    #[rstest]
864    fn render_inverted(empty_widget: QrCodeWidget) {
865        let mut buf = Buffer::empty(Rect::new(0, 0, 21, 11));
866        empty_widget
867            .colors(Colors::Inverted)
868            .render(buf.area, &mut buf);
869        assert_eq!(
870            buf,
871            Buffer::with_lines([
872                " ▄▄▄▄▄ ██▄▄▀ █ ▄▄▄▄▄ ",
873                " █   █ █ ▄▄█▄█ █   █ ",
874                " █▄▄▄█ █  ▀▀▄█ █▄▄▄█ ",
875                "▄▄▄▄▄▄▄█ ▀▄█▄█▄▄▄▄▄▄▄",
876                "▄█▄ ▄ ▄▀█▄  ▀▀ ▄▄ ▄▀█",
877                "▀▀▀███▄  ▄▀▀ ▀ ▄█▀█▀█",
878                "▄█▄█▄▄▄█ ▀ █ ██ ██ ██",
879                " ▄▄▄▄▄ █▀  ▄█▄█▀ ▄ ▄ ",
880                " █   █ █ ▄  ▀ ▀█▄ ▄▄▄",
881                " █▄▄▄█ █▄██▀ ▀ ▄█▀███",
882                "       ▀ ▀▀▀ ▀▀ ▀▀ ▀▀",
883            ])
884        );
885    }
886
887    #[rstest]
888    fn render_with_quiet_zone(empty_widget: QrCodeWidget) {
889        let mut buf = Buffer::empty(Rect::new(0, 0, 29, 15));
890        empty_widget
891            .quiet_zone(QuietZone::Enabled)
892            .render(buf.area, &mut buf);
893        assert_eq!(
894            buf,
895            Buffer::with_lines([
896                "                             ",
897                "                             ",
898                "    █▀▀▀▀▀█  ▀▀▄█ █▀▀▀▀▀█    ",
899                "    █ ███ █ █▀▀ ▀ █ ███ █    ",
900                "    █ ▀▀▀ █ ██▄▄▀ █ ▀▀▀ █    ",
901                "    ▀▀▀▀▀▀▀ █▄▀ ▀ ▀▀▀▀▀▀▀    ",
902                "    ▀ ▀█▀█▀▄ ▀██▄▄█▀▀█▀▄     ",
903                "    ▄▄▄   ▀██▀▄▄█▄█▀ ▄ ▄     ",
904                "    ▀ ▀ ▀▀▀ █▄█ █  █  █      ",
905                "    █▀▀▀▀▀█ ▄██▀ ▀ ▄█▀█▀█    ",
906                "    █ ███ █ █▀██▄█▄ ▀█▀▀▀    ",
907                "    █ ▀▀▀ █ ▀  ▄█▄█▀ ▄       ",
908                "    ▀▀▀▀▀▀▀ ▀   ▀  ▀  ▀      ",
909                "                             ",
910                "                             ",
911            ])
912        );
913    }
914
915    #[rstest]
916    fn render_with_quiet_zone_and_inverted(empty_widget: QrCodeWidget) {
917        let mut buf = Buffer::empty(Rect::new(0, 0, 29, 15));
918        empty_widget
919            .quiet_zone(QuietZone::Enabled)
920            .colors(Colors::Inverted)
921            .render(buf.area, &mut buf);
922        assert_eq!(
923            buf,
924            Buffer::with_lines([
925                "█████████████████████████████",
926                "█████████████████████████████",
927                "████ ▄▄▄▄▄ ██▄▄▀ █ ▄▄▄▄▄ ████",
928                "████ █   █ █ ▄▄█▄█ █   █ ████",
929                "████ █▄▄▄█ █  ▀▀▄█ █▄▄▄█ ████",
930                "████▄▄▄▄▄▄▄█ ▀▄█▄█▄▄▄▄▄▄▄████",
931                "████▄█▄ ▄ ▄▀█▄  ▀▀ ▄▄ ▄▀█████",
932                "████▀▀▀███▄  ▄▀▀ ▀ ▄█▀█▀█████",
933                "████▄█▄█▄▄▄█ ▀ █ ██ ██ ██████",
934                "████ ▄▄▄▄▄ █▀  ▄█▄█▀ ▄ ▄ ████",
935                "████ █   █ █ ▄  ▀ ▀█▄ ▄▄▄████",
936                "████ █▄▄▄█ █▄██▀ ▀ ▄█▀███████",
937                "████▄▄▄▄▄▄▄█▄███▄██▄██▄██████",
938                "█████████████████████████████",
939                "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
940            ])
941        );
942    }
943}