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//! 
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}