use qrcode::{EcLevel, QrCode};
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use crate::theme::Colors;
const QUIET: usize = 2;
pub fn lines(text: &str, colors: &Colors) -> Option<Vec<Line<'static>>> {
let code = QrCode::with_error_correction_level(text, EcLevel::L).ok()?;
let width = code.width();
let modules = code.to_colors();
let size = width + 2 * QUIET;
let dark = |x: usize, y: usize| {
(QUIET..QUIET + width).contains(&x)
&& (QUIET..QUIET + width).contains(&y)
&& modules[(y - QUIET) * width + x - QUIET] == qrcode::Color::Dark
};
let style = Style::new().fg(colors.qr_dark).bg(colors.qr_light);
let rows = (0..size)
.step_by(2)
.map(|y| {
let row: String = (0..size)
.map(|x| match (dark(x, y), dark(x, y + 1)) {
(true, true) => '█',
(true, false) => '▀',
(false, true) => '▄',
(false, false) => ' ',
})
.collect();
Line::from(Span::styled(row, style))
})
.collect();
Some(rows)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_drawn_code_scans_back_to_the_link() {
let link = "tg://login?token=AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA";
let rows = lines(link, &Colors::default()).unwrap();
let mut modules: Vec<Vec<bool>> = Vec::new();
for row in &rows {
let (top, bottom): (Vec<bool>, Vec<bool>) = row
.to_string()
.chars()
.map(|c| (matches!(c, '█' | '▀'), matches!(c, '█' | '▄')))
.unzip();
modules.extend([top, bottom]);
}
const SCALE: usize = 4;
const FRAME: usize = 4;
let width = (modules[0].len() + 2 * FRAME) * SCALE;
let height = (modules.len() + 2 * FRAME) * SCALE;
let mut image = rqrr::PreparedImage::prepare_from_greyscale(width, height, |x, y| {
let (x, y) = (
(x / SCALE).wrapping_sub(FRAME),
(y / SCALE).wrapping_sub(FRAME),
);
match modules.get(y).and_then(|row| row.get(x)) {
Some(false) => 255,
Some(true) | None => 0,
}
});
let grids = image.detect_grids();
assert_eq!(grids.len(), 1, "finds the code");
let (_, text) = grids[0].decode().unwrap();
assert_eq!(text, link);
}
}