use crate::render::{Canvas as RenderCanvas, Color, Pixel};
macro_rules! impl_bit_canvas {
($canvas:ident, $pixel:ident, $row_group:expr, $col_step:expr, $encode:expr) => {
pub struct $canvas {
canvas: Vec<u8>,
width: u32,
dark_pixel: u8,
}
impl RenderCanvas for $canvas {
type Pixel = $pixel;
type Image = String;
fn new(width: u32, height: u32, dark_pixel: $pixel, light_pixel: $pixel) -> Self {
let a = vec![light_pixel.value(); (width * height) as usize];
$canvas { 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 {
let w = self.width as usize;
let data = &self.canvas;
let row_group = $row_group;
let empty: &[u8] = &[];
let rows: Vec<&[u8]> = data.chunks_exact(w).collect();
let col_step: usize = $col_step;
let mut out = String::with_capacity(rows.len() / row_group * (w / col_step + 1));
for group in rows.chunks(row_group) {
let actual = group.len();
for col in (0..w).step_by(col_step) {
if actual == row_group {
out.push_str($encode(group, col));
} else {
let mut padded: [&[u8]; $row_group] = [empty; $row_group];
for i in 0..actual {
padded[i] = group[i];
}
out.push_str($encode(&padded, col));
}
}
out.push('\n');
}
if out.ends_with('\n') {
out.pop();
}
out
}
}
};
}
const CODEPAGE: [&str; 4] = [" ", "\u{2584}", "\u{2580}", "\u{2588}"];
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Dense1x2 {
Dark,
Light,
}
impl Pixel for Dense1x2 {
type Image = String;
type Canvas = Canvas1x2;
fn default_unit_size() -> (u32, u32) {
(1, 1)
}
fn default_color(color: Color) -> Dense1x2 {
color.select(Dense1x2::Dark, Dense1x2::Light)
}
}
impl Dense1x2 {
const fn value(self) -> u8 {
match self {
Dense1x2::Dark => 1,
Dense1x2::Light => 0,
}
}
}
fn encode_1x2(rows: &[&[u8]], col: usize) -> &'static str {
let top = rows[0].get(col).copied().unwrap_or(0);
let bot = rows[1].get(col).copied().unwrap_or(0);
CODEPAGE[usize::from(top * 2 + bot)]
}
impl_bit_canvas!(Canvas1x2, Dense1x2, 2, 1, encode_1x2 as fn(&[&[u8]], usize) -> &'static str);
const QUADRANT: [&str; 16] = [
" ", "\u{2598}", "\u{259D}", "\u{2580}", "\u{2596}", "\u{258C}", "\u{259E}", "\u{259B}", "\u{2597}", "\u{259A}", "\u{2590}", "\u{259C}", "\u{2584}", "\u{2599}", "\u{259F}", "\u{2588}", ];
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Dense2x2 {
Dark,
Light,
}
impl Pixel for Dense2x2 {
type Image = String;
type Canvas = Canvas2x2;
fn default_unit_size() -> (u32, u32) {
(1, 1)
}
fn default_color(color: Color) -> Dense2x2 {
color.select(Dense2x2::Dark, Dense2x2::Light)
}
}
impl Dense2x2 {
const fn value(self) -> u8 {
match self {
Dense2x2::Dark => 1,
Dense2x2::Light => 0,
}
}
}
fn encode_2x2(rows: &[&[u8]], col: usize) -> &'static str {
let tl = rows[0][col] & 1;
let tr = rows[0].get(col + 1).copied().unwrap_or(0) & 1;
let bl = rows[1].get(col).copied().unwrap_or(0) & 1;
let br = rows[1].get(col + 1).copied().unwrap_or(0) & 1;
QUADRANT[(tl | (tr << 1) | (bl << 2) | (br << 3)) as usize]
}
impl_bit_canvas!(Canvas2x2, Dense2x2, 2, 2, encode_2x2 as fn(&[&[u8]], usize) -> &'static str);
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Braille {
Dark,
Light,
}
impl Pixel for Braille {
type Image = String;
type Canvas = CanvasBraille;
fn default_unit_size() -> (u32, u32) {
(1, 1)
}
fn default_color(color: Color) -> Braille {
color.select(Braille::Dark, Braille::Light)
}
}
impl Braille {
const fn value(self) -> u8 {
match self {
Braille::Dark => 1,
Braille::Light => 0,
}
}
}
const BRAILLE_UTF8: [[u8; 3]; 256] = {
let mut table = [[0u8; 3]; 256];
let mut i = 0usize;
while i < 256 {
let cp = 0x2800u32 + i as u32;
table[i][0] = ((cp >> 12) & 0x0F) as u8 | 0xE0;
table[i][1] = ((cp >> 6) & 0x3F) as u8 | 0x80;
table[i][2] = (cp & 0x3F) as u8 | 0x80;
i += 1;
}
table
};
fn encode_braille(rows: &[&[u8]], col: usize) -> &'static str {
let d1 = rows[0].get(col).copied().unwrap_or(0) & 1;
let d2 = rows[1].get(col).copied().unwrap_or(0) & 1;
let d3 = rows[2].get(col).copied().unwrap_or(0) & 1;
let d4 = rows[0].get(col + 1).copied().unwrap_or(0) & 1;
let d5 = rows[1].get(col + 1).copied().unwrap_or(0) & 1;
let d6 = rows[2].get(col + 1).copied().unwrap_or(0) & 1;
let d7 = rows[3].get(col).copied().unwrap_or(0) & 1;
let d8 = rows[3].get(col + 1).copied().unwrap_or(0) & 1;
let bits = d1 | (d2 << 1) | (d3 << 2) | (d4 << 3) | (d5 << 4) | (d6 << 5) | (d7 << 6) | (d8 << 7);
unsafe { std::str::from_utf8_unchecked(&BRAILLE_UTF8[bits as usize]) }
}
impl_bit_canvas!(CanvasBraille, Braille, 4, 2, encode_braille as fn(&[&[u8]], usize) -> &'static str);
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Dense3x2 {
Dark,
Light,
}
impl Pixel for Dense3x2 {
type Image = String;
type Canvas = Canvas3x2;
fn default_unit_size() -> (u32, u32) {
(1, 1)
}
fn default_color(color: Color) -> Dense3x2 {
color.select(Dense3x2::Dark, Dense3x2::Light)
}
}
impl Dense3x2 {
const fn value(self) -> u8 {
match self {
Dense3x2::Dark => 1,
Dense3x2::Light => 0,
}
}
}
const SEXTANT_UTF8: [[u8; 4]; 64] = {
let mut table = [[0u8; 4]; 64];
let mut i = 0usize;
while i < 64 {
let cp = 0x1FB00u32 + i as u32;
table[i][0] = 0xF0u8 | ((cp >> 18) & 0x07) as u8;
table[i][1] = 0x80u8 | ((cp >> 12) & 0x3F) as u8;
table[i][2] = 0x80u8 | ((cp >> 6) & 0x3F) as u8;
table[i][3] = 0x80u8 | (cp & 0x3F) as u8;
i += 1;
}
table
};
fn encode_3x2(rows: &[&[u8]], col: usize) -> &'static str {
let d0 = rows[0].get(col).copied().unwrap_or(0) & 1;
let d1 = rows[1].get(col).copied().unwrap_or(0) & 1;
let d2 = rows[2].get(col).copied().unwrap_or(0) & 1;
let d3 = rows[0].get(col + 1).copied().unwrap_or(0) & 1;
let d4 = rows[1].get(col + 1).copied().unwrap_or(0) & 1;
let d5 = rows[2].get(col + 1).copied().unwrap_or(0) & 1;
let bits = d0 | (d1 << 1) | (d2 << 2) | (d3 << 3) | (d4 << 4) | (d5 << 5);
if bits == 0 {
" "
} else {
unsafe { std::str::from_utf8_unchecked(&SEXTANT_UTF8[bits as usize]) }
}
}
impl_bit_canvas!(Canvas3x2, Dense3x2, 3, 2, encode_3x2 as fn(&[&[u8]], usize) -> &'static str);
#[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::<Dense1x2>::new(colors, 2, 1).build();
assert_eq!(&image, " ▄ \n ▀ ");
let image2 = Renderer::<Dense1x2>::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::<Dense1x2>().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::<Dense1x2>()
.dark_color(Dense1x2::Light)
.light_color(Dense1x2::Dark)
.module_dimensions(1, 1)
.build();
assert_eq!(
image,
"█████████████████\n\
██ ▄▄▄▄▄ █▄▀▄█▄██\n\
██ █ █ █ █ ██\n\
██ █▄▄▄█ █▄▄██▀██\n\
██▄▄▄▄▄▄▄█▄▄▄▀ ██\n\
██▄ ▀ ▀ ▀▄▄ ████\n\
██▄▄▀▄█ ▀▀▀ ▀▄▄██\n\
██▄▄▄█▄▄█▄██▄█▄██\n\
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀"
);
}
#[test]
fn test_dense2x2_basic() {
use crate::render::Renderer;
let colors = &[Color::Dark, Color::Light, Color::Light, Color::Dark];
let image: String = Renderer::<Dense2x2>::new(colors, 2, 0).module_dimensions(1, 1).build();
assert_eq!(&image, "\u{259A}");
}
#[test]
fn test_dense2x2_with_quiet_zone() {
use crate::render::Renderer;
let colors = &[Color::Dark, Color::Light, Color::Light, Color::Dark];
let image: String = Renderer::<Dense2x2>::new(colors, 2, 1).build();
assert!(image.chars().count() >= 1);
}
#[test]
fn test_dense2x2_all_dark() {
use crate::render::Renderer;
let colors = vec![Color::Dark; 4];
let image: String = Renderer::<Dense2x2>::new(&colors, 2, 0).module_dimensions(1, 1).build();
assert_eq!(&image, "\u{2588}");
}
#[test]
fn test_dense2x2_all_light() {
use crate::render::Renderer;
let colors = vec![Color::Light; 4];
let image: String = Renderer::<Dense2x2>::new(&colors, 2, 0).module_dimensions(1, 1).build();
assert_eq!(&image, " ");
}
#[test]
fn integration_render_utf8_2x2() {
use crate::render::unicode::Dense2x2;
use crate::{EcLevel, QrCode, Version};
let code = QrCode::with_version(b"09876542", Version::Micro(2), EcLevel::L).unwrap();
let image = code.render::<Dense2x2>().module_dimensions(1, 1).build();
assert!(!image.is_empty());
let dense1x2 = code.render::<Dense1x2>().module_dimensions(1, 1).build();
assert!(image.len() < dense1x2.len());
}
#[test]
fn test_braille_all_dark() {
use crate::render::Renderer;
let colors = vec![Color::Dark; 16];
let image: String = Renderer::<Braille>::new(&colors, 4, 0).module_dimensions(1, 1).build();
assert_eq!(&image, "\u{28FF}\u{28FF}");
}
#[test]
fn test_braille_all_light() {
use crate::render::Renderer;
let colors = vec![Color::Light; 16];
let image: String = Renderer::<Braille>::new(&colors, 4, 0).module_dimensions(1, 1).build();
assert_eq!(&image, "\u{2800}\u{2800}");
}
#[test]
fn test_braille_top_left_dot() {
use crate::render::Renderer;
let mut colors = vec![Color::Light; 16];
colors[0] = Color::Dark;
let image: String = Renderer::<Braille>::new(&colors, 4, 0).module_dimensions(1, 1).build();
assert_eq!(&image, "\u{2801}\u{2800}");
}
#[test]
fn test_braille_density() {
use crate::render::unicode::{Braille, Dense1x2};
use crate::{EcLevel, QrCode, Version};
let code = QrCode::with_version(b"09876542", Version::Micro(2), EcLevel::L).unwrap();
let braille = code.render::<Braille>().module_dimensions(1, 1).build();
let dense1x2 = code.render::<Dense1x2>().module_dimensions(1, 1).build();
assert!(braille.len() < dense1x2.len());
}
#[test]
fn test_dense3x2_all_dark() {
use crate::render::Renderer;
let colors = vec![Color::Dark; 36];
let image: String = Renderer::<Dense3x2>::new(&colors, 6, 0).module_dimensions(1, 1).build();
assert_eq!(&image, "\u{1FB3F}\u{1FB3F}\u{1FB3F}\n\u{1FB3F}\u{1FB3F}\u{1FB3F}");
}
#[test]
fn test_dense3x2_all_light() {
use crate::render::Renderer;
let colors = vec![Color::Light; 36];
let image: String = Renderer::<Dense3x2>::new(&colors, 6, 0).module_dimensions(1, 1).build();
assert_eq!(&image, " \n ");
}
#[test]
fn test_dense3x2_top_left_cell() {
use crate::render::Renderer;
let mut colors = vec![Color::Light; 36];
colors[0] = Color::Dark;
let image: String = Renderer::<Dense3x2>::new(&colors, 6, 0).module_dimensions(1, 1).build();
assert!(image.starts_with('\u{1FB01}'));
}
#[test]
fn test_dense3x2_density() {
use crate::render::unicode::{Dense1x2, Dense3x2};
use crate::{EcLevel, QrCode, Version};
let code = QrCode::with_version(b"09876542", Version::Micro(2), EcLevel::L).unwrap();
let sextant = code.render::<Dense3x2>().module_dimensions(1, 1).build();
let dense1x2 = code.render::<Dense1x2>().module_dimensions(1, 1).build();
assert!(sextant.len() < dense1x2.len());
}