use crossterm::Colorize;
pub use qrcode::types::QrError;
use qrcode::{
types::Color::{self as QrColor, Dark as QrDark, Light as QrLight},
QrCode,
};
const QUIET_ZONE_WIDTH: usize = 2;
pub fn print_qr(text: &str) -> Result<(), QrError> {
Renderer::new().print_qr(text)
}
struct Renderer {}
impl Renderer {
pub fn new() -> Self {
Renderer {}
}
pub fn print_qr(&mut self, text: &str) -> Result<(), QrError> {
let pixels = QrCode::new(text)?.into_colors();
let pixels = Self::surround_quiet(&pixels, QUIET_ZONE_WIDTH, QrLight);
self.print_matrix(&pixels);
Ok(())
}
fn print_matrix(&mut self, pixels: &[QrColor]) {
let width = usize_sqrt(pixels.len());
for row in 0..width / 2 {
for col in 0..width {
let vec_pos = (row * 2) * width + col;
let vec_pos_below = (row * 2 + 1) * width + col;
match (pixels[vec_pos], pixels[vec_pos_below]) {
(QrDark, QrDark) => self.black_above_black(),
(QrDark, QrLight) => self.black_above_white(),
(QrLight, QrDark) => self.white_above_black(),
(QrLight, QrLight) => self.white_above_white(),
};
}
self.newline();
}
if width % 2 == 1 {
for col in 0..width {
let vec_pos = width * (width - 1) + col;
match pixels[vec_pos] {
QrDark => self.black_above_white(),
QrLight => self.white_above_white(),
};
}
self.newline()
}
}
fn surround_quiet<T: Copy>(pixels: &[T], thickness: usize, quiet: T) -> Vec<T> {
let width = usize_sqrt(pixels.len());
let out_width = width + thickness * 2;
let mut out = vec![quiet; out_width.pow(2)];
for vec_row in 0..width {
for vec_col in 0..width {
let vec_pos = width * vec_row + vec_col;
let out_row = vec_row + thickness;
let out_col = vec_col + thickness;
let out_pos = out_row * out_width + out_col;
out[out_pos] = pixels[vec_pos]
}
}
out
}
fn black_above_white(&self) {
print!("{}", "▄".white().on_black());
}
fn white_above_black(&self) {
print!("{}", "▄".black().on_white());
}
fn black_above_black(&self) {
print!("{}", " ".white().on_black());
}
fn white_above_white(&self) {
print!("{}", " ".black().on_white());
}
fn newline(&mut self) {
println!();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn print_matrix_incorrect_size() {
Renderer::new().print_matrix(&vec![QrDark, QrDark, QrLight, QrLight, QrLight, QrDark]);
}
#[test]
fn surround_quiet_normal() {
let input = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
let expected = vec![
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 0, 1, 2, 9, 9, 9, 9, 9, 9, 3, 4, 5, 9, 9, 9, 9, 9, 9, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
];
let actual = Renderer::surround_quiet(&input, 3, 9);
assert_eq!(expected, actual);
}
#[test]
fn surround_quiet_empty() {
let actual = Renderer::surround_quiet(&[], 3, 7);
let expected = vec![7; (3 * 2) * (3 * 2)];
assert_eq!(expected, actual);
}
#[test]
fn print_qr_too_long() {
print_qr(&String::from_utf8(vec![b'a'; 8000]).unwrap())
.err()
.unwrap();
}
}
#[inline(always)]
fn usize_sqrt(num: usize) -> usize {
let sqrt = (num as f64).sqrt() as usize;
assert_eq!(num, sqrt * sqrt, "given number isn't a multiple of 2");
sqrt as usize
}