use qrcode::{EcLevel, QrCode};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::widgets::Widget;
use tui_qrcode::{Colors, QrCodeWidget, QuietZone};
pub(super) struct Qr {
widget: QrCodeWidget,
pub width: u16,
pub height: u16,
}
pub(super) fn encode(url: &str) -> Option<Qr> {
let code = QrCode::with_error_correction_level(url.as_bytes(), EcLevel::L).ok()?;
let widget = QrCodeWidget::new(code)
.quiet_zone(QuietZone::Enabled)
.colors(colors())
.style(style());
let size = widget.size(Rect::default());
Some(Qr {
widget,
width: size.width,
height: size.height,
})
}
impl Qr {
pub fn fits(&self, width: u16, height: u16) -> bool {
self.width <= width && self.height <= height
}
pub fn draw(&self, area: Rect, buf: &mut Buffer) {
if !self.fits(area.width, area.height) {
return;
}
let at = Rect {
x: area.x + (area.width - self.width) / 2,
y: area.y,
width: self.width,
height: self.height,
};
(&self.widget).render(at, buf);
}
}
fn style() -> Style {
match super::theme::no_color() {
true => Style::default(),
false => Style::default()
.fg(Color::Indexed(16))
.bg(Color::Indexed(231)),
}
}
fn colors() -> Colors {
match super::theme::no_color() {
true => Colors::Inverted,
false => Colors::Normal,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_code_is_as_big_as_its_link_needs_and_no_bigger() {
let short = encode("https://example.com/").expect("encodes");
assert_eq!((short.width, short.height), (33, 17));
let link = format!(
"https://tribute-resistance-resolved-moscow.trycloudflare.com/?t={}",
"0123456789abcdef".repeat(2)
);
let long = encode(&link).expect("encodes");
assert!(long.width > short.width && long.height > short.height);
assert_eq!(long.height, long.width.div_ceil(2));
}
#[test]
fn a_code_that_does_not_fit_draws_nothing() {
let qr = encode("https://example.com/").expect("encodes");
let area = Rect::new(0, 0, qr.width - 1, qr.height);
let mut buf = Buffer::empty(area);
qr.draw(area, &mut buf);
assert_eq!(buf, Buffer::empty(area));
let area = Rect::new(0, 0, qr.width + 4, qr.height);
let mut buf = Buffer::empty(area);
qr.draw(area, &mut buf);
assert_ne!(buf, Buffer::empty(area));
assert_eq!(buf[(1, 0)], Buffer::empty(area)[(1, 0)]);
assert_eq!(buf[(qr.width + 2, 0)], Buffer::empty(area)[(0, 0)]);
}
#[test]
fn a_code_is_drawn_dark_on_light() {
let qr = encode("https://example.com/").expect("encodes");
let area = Rect::new(0, 0, qr.width, qr.height);
let mut buf = Buffer::empty(area);
qr.draw(area, &mut buf);
let corner = &buf[(0, 0)];
assert_eq!(corner.symbol(), " ");
assert_eq!(corner.bg, Color::Indexed(231));
assert_eq!(corner.fg, Color::Indexed(16));
assert_eq!(buf[(4, 2)].symbol(), "█");
}
}