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, DefaultTerminal, Frame};
29//! use tui_qrcode::{Colors, QrCodeWidget};
30//!
31//! fn main() -> color_eyre::Result<()> {
32//!     color_eyre::install()?;
33//!     let terminal = ratatui::init();
34//!     let result = run(terminal);
35//!     ratatui::restore();
36//!     result
37//! }
38//!
39//! fn run(mut terminal: DefaultTerminal) -> color_eyre::Result<()> {
40//!     loop {
41//!         terminal.draw(render)?;
42//!         if matches!(event::read()?, event::Event::Key(_)) {
43//!             break Ok(());
44//!         }
45//!     }
46//! }
47//!
48//! fn render(frame: &mut Frame) {
49//!     let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
50//!     let widget = QrCodeWidget::new(qr_code).colors(Colors::Inverted);
51//!     frame.render_widget(widget, frame.area());
52//! }
53//! ```
54//!
55//! Renders the following QR code:
56//!
57//! ```text
58//! █████████████████████████████████
59//! █████████████████████████████████
60//! ████ ▄▄▄▄▄ █▄ ▄▄▄ ████ ▄▄▄▄▄ ████
61//! ████ █   █ █▄▄▄█▀▄██ █ █   █ ████
62//! ████ █▄▄▄█ █▀   ▄▀ ███ █▄▄▄█ ████
63//! ████▄▄▄▄▄▄▄█▄▀▄█ ▀▄▀ █▄▄▄▄▄▄▄████
64//! ████ █▄▀▀▀▄▄▀▄▄  ▄█▀▄█▀ █▀▄▀ ████
65//! ██████▀█  ▄▀▄▄▀▀ ▄ ▄█ ▄▄█ ▄█▄████
66//! ████▄▀▀▀▄▄▄▄▀█▄▄█  ▀ ▀ ▀███▀ ████
67//! ████▄▄ ▀█▄▄▀▄▄ ▄█▀█▄▀█▄▀▀ ▄█▄████
68//! ████▄▄█▄██▄█ ▄▀▄ ▄█  ▄▄▄ ██▄▀████
69//! ████ ▄▄▄▄▄ █▄▄▄▀ ▄ ▀ █▄█ ███ ████
70//! ████ █   █ ██ ███  ▄▄ ▄▄ █▀ ▄████
71//! ████ █▄▄▄█ █▄▀ ▄█▀█▀ ▄█  ▄█▄▄████
72//! ████▄▄▄▄▄▄▄█▄▄█▄▄▄██▄█▄██▄██▄████
73//! █████████████████████████████████
74//! ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
75//! ```
76//!
77//! [Ratatui]: https://crates.io/crates/ratatui
78//! [Crate badge]: https://img.shields.io/crates/v/tui-qrcode.svg?style=for-the-badge
79//! [tui-qrcode]: https://crates.io/crates/tui-qrcode
80//! [Docs.rs Badge]: https://img.shields.io/badge/docs.rs-tui--qrcode-blue?style=for-the-badge
81//! [API Docs]: https://docs.rs/tui-qrcode
82//! [Deps.rs Badge]: https://deps.rs/repo/github/joshka/tui-qrcode/status.svg?style=for-the-badge
83//! [Dependency Status]: https://deps.rs/repo/github/joshka/tui-qrcode
84//! [License Badge]: https://img.shields.io/crates/l/tui-qrcode?style=for-the-badge
85//! [Discord Badge]:
86//!     https://img.shields.io/discord/1070692720437383208?label=ratatui+discord&logo=discord&style=for-the-badge
87//! [Ratatui Discord]: https://discord.gg/pMCEU9hNEj
88//! [GitHub Repository]: https://github.com/joshka/tui-widgets
89//! [Examples]: https://github.com/joshka/tui-widgets/tree/main/tui-qrcode/examples
90//! [Changelog]: https://github.com/joshka/tui-widgets/blob/main/tui-qrcode/CHANGELOG.md
91//! [Contributing]: https://github.com/joshka/tui-widgets/blob/main/CONTRIBUTING.md
92
93use qrcode::{render::unicode::Dense1x2, QrCode};
94use ratatui::{
95    buffer::Buffer,
96    layout::Rect,
97    style::{Style, Styled},
98    text::Text,
99    widgets::Widget,
100};
101
102/// A [Ratatui](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/// [Stylize](ratatui::style::Stylize) trait can be used.
120///
121/// ```no_run
122/// use qrcode::QrCode;
123/// use tui_qrcode::{Colors, QrCodeWidget, QuietZone, Scaling};
124/// use ratatui::{Frame, style::{Style, Stylize}};
125///
126/// fn render(frame: &mut Frame) {
127///     let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
128///     let widget = QrCodeWidget::new(qr_code)
129///         .quiet_zone(QuietZone::Disabled)
130///         .scaling(Scaling::Max)
131///         .colors(Colors::Inverted)
132///         .red()
133///         .on_light_yellow();
134///     frame.render_widget(widget, frame.area());
135/// }
136/// ```
137pub struct QrCodeWidget {
138    qr_code: QrCode,
139    quiet_zone: QuietZone,
140    scaling: Scaling,
141    colors: Colors,
142    style: Style,
143}
144
145/// The quiet zone (border) of a QR code.
146#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
147pub enum QuietZone {
148    /// The quiet zone is enabled.
149    #[default]
150    Enabled,
151    /// The quiet zone is disabled.
152    Disabled,
153}
154
155#[derive(Debug, Clone, Copy, Eq, PartialEq)]
156pub enum Scaling {
157    /// The QR code will be scaled to at least the size of the widget.
158    ///
159    /// Note that this usually results in a QR code that is larger than the widget, which is not
160    /// ideal.
161    Min,
162
163    /// The QR code will be scaled to be at most the size of the widget.
164    ///
165    /// Note that this may result in a QR code which is scaled more horizontally or vertically than
166    /// the other, which may not be ideal.
167    Max,
168
169    /// The QR code will be scaled so each pixel is the size of the given dimensions.
170    ///
171    /// The minimum dimensions are 1x1 (width x height).
172    Exact(u32, u32),
173}
174
175impl Default for Scaling {
176    fn default() -> Self {
177        Self::Exact(1, 1)
178    }
179}
180
181#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
182pub enum Colors {
183    /// The default colors. (Black on white)
184    #[default]
185    Normal,
186
187    /// The colors are inverted. (White on black)
188    Inverted,
189}
190
191impl QrCodeWidget {
192    /// Create a new QR code widget.
193    #[must_use]
194    pub fn new(qr_code: QrCode) -> Self {
195        Self {
196            qr_code,
197            quiet_zone: QuietZone::default(),
198            scaling: Scaling::default(),
199            colors: Colors::default(),
200            style: Style::default(),
201        }
202    }
203
204    /// Set whether the QR code should have a quiet zone.
205    ///
206    /// This is the white border around the QR code. By default, the quiet zone is enabled.
207    ///
208    /// # Example
209    ///
210    /// ```
211    /// use qrcode::QrCode;
212    /// use tui_qrcode::{QrCodeWidget, QuietZone};
213    ///
214    /// let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
215    /// let widget = QrCodeWidget::new(qr_code).quiet_zone(QuietZone::Disabled);
216    /// ```
217    #[must_use]
218    pub fn quiet_zone(mut self, quiet_zone: QuietZone) -> Self {
219        self.quiet_zone = quiet_zone;
220        self
221    }
222
223    /// Set how the QR code should be scaled.
224    ///
225    /// By default, the QR code will be scaled so each pixel is 1x1.
226    ///
227    /// The `Min` variant will scale the QR code so it is at least the size of the widget. This may
228    /// result in a QR code that is larger than the widget, which is not ideal. The `Max` variant
229    /// will scale the QR code so it is at most the size of the widget. This may result in a QR code
230    /// which is scaled more horizontally or vertically than the other, which may not be ideal. The
231    /// `Exact` variant will scale the QR code so each pixel is the size of the given dimensions.
232    /// The minimum dimensions are 1x1 (width x height).
233    ///
234    /// # Example
235    ///
236    /// ```
237    /// use qrcode::QrCode;
238    /// use tui_qrcode::{QrCodeWidget, Scaling};
239    ///
240    /// let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
241    /// let widget = QrCodeWidget::new(qr_code).scaling(Scaling::Max);
242    /// ```
243    #[must_use]
244    pub fn scaling(mut self, scaling: Scaling) -> Self {
245        self.scaling = scaling;
246        self
247    }
248
249    /// Set the colors of the QR code.
250    ///
251    /// By default, the colors are normal (light on dark).
252    ///
253    /// The `Normal` variant will use the default colors. The `Inverted` variant will invert the
254    /// colors (dark on light).
255    ///
256    /// To set the foreground and background colors of the widget, use the `style` method.
257    ///
258    /// # Example
259    ///
260    /// ```
261    /// use qrcode::QrCode;
262    /// use tui_qrcode::{QrCodeWidget, Colors};
263    ///
264    /// let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
265    /// let widget = QrCodeWidget::new(qr_code).colors(Colors::Inverted);
266    /// ```
267    #[must_use]
268    pub fn colors(mut self, colors: Colors) -> Self {
269        self.colors = colors;
270        self
271    }
272
273    /// Set the style of the widget.
274    ///
275    /// This will set the foreground and background colors of the widget.
276    ///
277    /// # Example
278    ///
279    /// ```
280    /// use qrcode::QrCode;
281    /// use tui_qrcode::QrCodeWidget;
282    /// use ratatui::style::{Style, Stylize};
283    ///
284    /// let qr_code = QrCode::new("https://ratatui.rs").expect("failed to create QR code");
285    /// let style = Style::new().red().on_light_yellow();
286    /// let widget = QrCodeWidget::new(qr_code).style(style);
287    /// ```
288    #[must_use]
289    pub fn style(mut self, style: impl Into<Style>) -> Self {
290        self.style = style.into();
291        self
292    }
293}
294
295impl Styled for QrCodeWidget {
296    type Item = Self;
297
298    fn style(&self) -> Style {
299        self.style
300    }
301
302    fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
303        self.style(style)
304    }
305}
306
307impl Widget for QrCodeWidget {
308    fn render(self, area: Rect, buf: &mut Buffer) {
309        (&self).render(area, buf);
310    }
311}
312
313impl Widget for &QrCodeWidget {
314    fn render(self, area: Rect, buf: &mut Buffer) {
315        let mut renderer = self.qr_code.render::<Dense1x2>();
316        match self.quiet_zone {
317            QuietZone::Enabled => renderer.quiet_zone(true),
318            QuietZone::Disabled => renderer.quiet_zone(false),
319        };
320        match self.scaling {
321            Scaling::Min => renderer.min_dimensions(area.width as u32, area.height as u32 * 2),
322            Scaling::Max => renderer.max_dimensions(area.width as u32, area.height as u32 * 2),
323            Scaling::Exact(width, height) => renderer.module_dimensions(width, height),
324        };
325        match self.colors {
326            Colors::Normal => renderer
327                .dark_color(Dense1x2::Dark)
328                .light_color(Dense1x2::Light),
329            Colors::Inverted => renderer
330                .dark_color(Dense1x2::Light)
331                .light_color(Dense1x2::Dark),
332        };
333        Text::raw(renderer.build())
334            .style(self.style)
335            .render(area, buf);
336    }
337}