//! UTF-8 rendering, with 2 pixels per symbol. use alloc::{ string::String, vec::{self, Vec}, }; use crate::render::{Canvas as RenderCanvas, Color, Pixel}; const CODEPAGE: [&str; 4] = [" ", "\u{2584}", "\u{2580}", "\u{2588}"]; #[derive(Copy, Clone, PartialEq)] pub enum Dense1x2 { Dark, Light, } impl Pixel for Dense1x2 { type Image = String; type Canvas = Canvas1x2; fn default_color(color: Color) -> Dense1x2 { color.select(Dense1x2::Dark, Dense1x2::Light) } fn default_unit_size() -> (u32, u32) { (1, 1) } } impl Dense1x2 { fn value(self) -> u8 { match self { Dense1x2::Dark => 1, Dense1x2::Light => 0, } } fn parse_2_bits(sym: u8) -> &'static str { CODEPAGE[usize::from(sym)] } } pub struct Canvas1x2 { canvas: Vec, width: u32, dark_pixel: u8, } impl RenderCanvas for Canvas1x2 { type Pixel = Dense1x2; type Image = String; fn new(width: u32, height: u32, dark_pixel: Dense1x2, light_pixel: Dense1x2) -> Self { let a = vec![light_pixel.value(); (width * height) as usize]; Canvas1x2 { width, canvas: a, dark_pixel: dark_pixel.value() } } fn draw_dark_pixel(&mut self, x: u32, y: u32) { self.canvas[(x + y * self.width) as usize] = self.dark_pixel; } fn into_image(self) -> String { self.canvas // Chopping array into 1-line sized fragments .chunks_exact(self.width as usize) .collect::>() // And then glueing every 2 lines. .chunks(2) .map(|rows| { { // Then zipping those 2 lines together into a single 2-bit number list. if rows.len() == 2 { rows[0].iter().zip(rows[1]).map(|(top, bot)| (top * 2 + bot)).collect::>() } else { rows[0].iter().map(|top| (top * 2)).collect::>() } } .into_iter() // Mapping those 2-bit numbers to corresponding pixels. .map(Dense1x2::parse_2_bits) .collect::>() .concat() }) .collect::>() .join("\n") } } #[test] fn test_render_to_utf8_string() { use crate::render::Renderer; let colors = &[Color::Dark, Color::Light, Color::Light, Color::Dark]; let image: String = Renderer::::new(colors, 2, 1).build(); assert_eq!(&image, " ▄ \n ▀ "); let image2 = Renderer::::new(colors, 2, 1).module_dimensions(2, 2).build(); assert_eq!(&image2, " \n ██ \n ██ \n "); } #[test] fn integration_render_utf8_1x2() { use crate::render::unicode::Dense1x2; use crate::{EcLevel, QrCode, Version}; let code = QrCode::with_version(b"09876542", Version::Micro(2), EcLevel::L).unwrap(); let image = code.render::().module_dimensions(1, 1).build(); assert_eq!( image, String::new() + " \n" + " █▀▀▀▀▀█ ▀ █ ▀ \n" + " █ ███ █ ▀ █ \n" + " █ ▀▀▀ █ ▀█ █ \n" + " ▀▀▀▀▀▀▀ ▄▀▀ █ \n" + " ▀█ ▀▀▀▀▀██▀▀▄ \n" + " ▀███▄ ▀▀ █ ██ \n" + " ▀▀▀ ▀ ▀▀ ▀ ▀ \n" + " " ) } #[test] fn integration_render_utf8_1x2_inverted() { use crate::render::unicode::Dense1x2; use crate::{EcLevel, QrCode, Version}; let code = QrCode::with_version(b"12345678", Version::Micro(2), EcLevel::L).unwrap(); let image = code .render::() .dark_color(Dense1x2::Light) .light_color(Dense1x2::Dark) .module_dimensions(1, 1) .build(); assert_eq!( image, "█████████████████\n\ ██ ▄▄▄▄▄ █▄▀▄█▄██\n\ ██ █ █ █ █ ██\n\ ██ █▄▄▄█ █▄▄██▀██\n\ ██▄▄▄▄▄▄▄█▄▄▄▀ ██\n\ ██▄ ▀ ▀ ▀▄▄ ████\n\ ██▄▄▀▄█ ▀▀▀ ▀▄▄██\n\ ██▄▄▄█▄▄█▄██▄█▄██\n\ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀" ); }