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, 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}