1#[cfg(not(feature = "std"))]
4#[allow(unused_imports)]
5use alloc::{
6 borrow::ToOwned,
7 format,
8 string::{String, ToString},
9 vec,
10 vec::Vec,
11};
12
13use crate::{Canvas as RenderCanvas, Color, Pixel};
14
15macro_rules! impl_bit_canvas {
21 ($canvas:ident, $pixel:ident, $row_group:expr, $col_step:expr, $encode:expr) => {
22 #[doc(hidden)]
23 pub struct $canvas {
24 canvas: Vec<u8>,
25 width: u32,
26 dark_pixel: u8,
27 }
28
29 impl RenderCanvas for $canvas {
30 type Pixel = $pixel;
31 type Image = String;
32
33 fn new(width: u32, height: u32, dark_pixel: $pixel, light_pixel: $pixel) -> Self {
34 let a = vec![light_pixel.value(); (width * height) as usize];
35 $canvas { width, canvas: a, dark_pixel: dark_pixel.value() }
36 }
37
38 fn draw_dark_pixel(&mut self, x: u32, y: u32) {
39 self.canvas[(x + y * self.width) as usize] = self.dark_pixel;
40 }
41
42 fn into_image(self) -> String {
43 let w = self.width as usize;
44 let data = &self.canvas;
45 let row_group = $row_group;
46 let empty: &[u8] = &[];
47 let rows: Vec<&[u8]> = data.chunks_exact(w).collect();
48 let col_step: usize = $col_step;
49 let mut out = String::with_capacity(rows.len() / row_group * (w / col_step + 1));
50
51 for group in rows.chunks(row_group) {
52 let actual = group.len();
53 for col in (0..w).step_by(col_step) {
54 if actual == row_group {
55 out.push_str($encode(group, col));
56 } else {
57 let mut padded: [&[u8]; $row_group] = [empty; $row_group];
58 for i in 0..actual {
59 padded[i] = group[i];
60 }
61 out.push_str($encode(&padded, col));
62 }
63 }
64 out.push('\n');
65 }
66 if out.ends_with('\n') {
67 out.pop();
68 }
69 out
70 }
71 }
72 };
73}
74
75const CODEPAGE: [&str; 4] = [" ", "\u{2584}", "\u{2580}", "\u{2588}"];
79
80#[derive(Copy, Clone, PartialEq, Eq)]
83pub enum Dense1x2 {
84 Dark,
86 Light,
88}
89
90impl Pixel for Dense1x2 {
91 type Image = String;
92 type Canvas = Canvas1x2;
93 fn default_unit_size() -> (u32, u32) {
94 (1, 1)
95 }
96 fn default_color(color: Color) -> Dense1x2 {
97 color.select(Dense1x2::Dark, Dense1x2::Light)
98 }
99}
100
101impl Dense1x2 {
102 const fn value(self) -> u8 {
103 match self {
104 Dense1x2::Dark => 1,
105 Dense1x2::Light => 0,
106 }
107 }
108}
109
110fn encode_1x2(rows: &[&[u8]], col: usize) -> &'static str {
111 let top = rows[0].get(col).copied().unwrap_or(0);
112 let bot = rows[1].get(col).copied().unwrap_or(0);
113 CODEPAGE[usize::from(top * 2 + bot)]
114}
115
116impl_bit_canvas!(Canvas1x2, Dense1x2, 2, 1, encode_1x2 as fn(&[&[u8]], usize) -> &'static str);
117
118const QUADRANT: [&str; 16] = [
124 " ", "\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}", ];
141
142#[derive(Copy, Clone, PartialEq, Eq)]
145pub enum Dense2x2 {
146 Dark,
148 Light,
150}
151
152impl Pixel for Dense2x2 {
153 type Image = String;
154 type Canvas = Canvas2x2;
155 fn default_unit_size() -> (u32, u32) {
156 (1, 1)
157 }
158 fn default_color(color: Color) -> Dense2x2 {
159 color.select(Dense2x2::Dark, Dense2x2::Light)
160 }
161}
162
163impl Dense2x2 {
164 const fn value(self) -> u8 {
165 match self {
166 Dense2x2::Dark => 1,
167 Dense2x2::Light => 0,
168 }
169 }
170}
171
172fn encode_2x2(rows: &[&[u8]], col: usize) -> &'static str {
173 let tl = rows[0][col] & 1;
174 let tr = rows[0].get(col + 1).copied().unwrap_or(0) & 1;
175 let bl = rows[1].get(col).copied().unwrap_or(0) & 1;
176 let br = rows[1].get(col + 1).copied().unwrap_or(0) & 1;
177 QUADRANT[(tl | (tr << 1) | (bl << 2) | (br << 3)) as usize]
178}
179
180impl_bit_canvas!(Canvas2x2, Dense2x2, 2, 2, encode_2x2 as fn(&[&[u8]], usize) -> &'static str);
181
182#[derive(Copy, Clone, PartialEq, Eq)]
203pub enum Braille {
204 Dark,
206 Light,
208}
209
210impl Pixel for Braille {
211 type Image = String;
212 type Canvas = CanvasBraille;
213 fn default_unit_size() -> (u32, u32) {
214 (1, 1)
215 }
216 fn default_color(color: Color) -> Braille {
217 color.select(Braille::Dark, Braille::Light)
218 }
219}
220
221impl Braille {
222 const fn value(self) -> u8 {
223 match self {
224 Braille::Dark => 1,
225 Braille::Light => 0,
226 }
227 }
228}
229
230const BRAILLE_UTF8: [[u8; 3]; 256] = {
232 let mut table = [[0u8; 3]; 256];
233 let mut i = 0usize;
234 while i < 256 {
235 let cp = 0x2800u32 + i as u32;
236 table[i][0] = ((cp >> 12) & 0x0F) as u8 | 0xE0;
237 table[i][1] = ((cp >> 6) & 0x3F) as u8 | 0x80;
238 table[i][2] = (cp & 0x3F) as u8 | 0x80;
239 i += 1;
240 }
241 table
242};
243
244fn encode_braille(rows: &[&[u8]], col: usize) -> &'static str {
245 let d1 = rows[0].get(col).copied().unwrap_or(0) & 1;
246 let d2 = rows[1].get(col).copied().unwrap_or(0) & 1;
247 let d3 = rows[2].get(col).copied().unwrap_or(0) & 1;
248 let d4 = rows[0].get(col + 1).copied().unwrap_or(0) & 1;
249 let d5 = rows[1].get(col + 1).copied().unwrap_or(0) & 1;
250 let d6 = rows[2].get(col + 1).copied().unwrap_or(0) & 1;
251 let d7 = rows[3].get(col).copied().unwrap_or(0) & 1;
252 let d8 = rows[3].get(col + 1).copied().unwrap_or(0) & 1;
253
254 let bits = d1 | (d2 << 1) | (d3 << 2) | (d4 << 3) | (d5 << 4) | (d6 << 5) | (d7 << 6) | (d8 << 7);
255 unsafe { core::str::from_utf8_unchecked(&BRAILLE_UTF8[bits as usize]) }
257}
258
259impl_bit_canvas!(CanvasBraille, Braille, 4, 2, encode_braille as fn(&[&[u8]], usize) -> &'static str);
260
261#[derive(Copy, Clone, PartialEq, Eq)]
290pub enum Dense3x2 {
291 Dark,
293 Light,
295}
296
297impl Pixel for Dense3x2 {
298 type Image = String;
299 type Canvas = Canvas3x2;
300 fn default_unit_size() -> (u32, u32) {
301 (1, 1)
302 }
303 fn default_color(color: Color) -> Dense3x2 {
304 color.select(Dense3x2::Dark, Dense3x2::Light)
305 }
306}
307
308impl Dense3x2 {
309 const fn value(self) -> u8 {
310 match self {
311 Dense3x2::Dark => 1,
312 Dense3x2::Light => 0,
313 }
314 }
315}
316
317const SEXTANT_UTF8: [[u8; 4]; 64] = {
320 let mut table = [[0u8; 4]; 64];
321 let mut i = 0usize;
322 while i < 64 {
323 let cp = 0x1FB00u32 + i as u32;
324 table[i][0] = 0xF0u8 | ((cp >> 18) & 0x07) as u8;
325 table[i][1] = 0x80u8 | ((cp >> 12) & 0x3F) as u8;
326 table[i][2] = 0x80u8 | ((cp >> 6) & 0x3F) as u8;
327 table[i][3] = 0x80u8 | (cp & 0x3F) as u8;
328 i += 1;
329 }
330 table
331};
332
333fn encode_3x2(rows: &[&[u8]], col: usize) -> &'static str {
336 let d0 = rows[0].get(col).copied().unwrap_or(0) & 1;
337 let d1 = rows[1].get(col).copied().unwrap_or(0) & 1;
338 let d2 = rows[2].get(col).copied().unwrap_or(0) & 1;
339 let d3 = rows[0].get(col + 1).copied().unwrap_or(0) & 1;
340 let d4 = rows[1].get(col + 1).copied().unwrap_or(0) & 1;
341 let d5 = rows[2].get(col + 1).copied().unwrap_or(0) & 1;
342
343 let bits = d0 | (d1 << 1) | (d2 << 2) | (d3 << 3) | (d4 << 4) | (d5 << 5);
344 if bits == 0 {
345 " "
346 } else {
347 unsafe { core::str::from_utf8_unchecked(&SEXTANT_UTF8[bits as usize]) }
349 }
350}
351
352impl_bit_canvas!(Canvas3x2, Dense3x2, 3, 2, encode_3x2 as fn(&[&[u8]], usize) -> &'static str);
353
354#[test]
357fn test_render_to_utf8_string() {
358 use crate::Renderer;
359 let colors = &[Color::Dark, Color::Light, Color::Light, Color::Dark];
360 let image: String = Renderer::<Dense1x2>::new(colors, 2, 1).build();
361
362 assert_eq!(&image, " ▄ \n ▀ ");
363
364 let image2 = Renderer::<Dense1x2>::new(colors, 2, 1).module_dimensions(2, 2).build();
365
366 assert_eq!(&image2, " \n ██ \n ██ \n ");
367}
368
369#[test]
370fn integration_render_utf8_1x2() {
371 use crate::Renderer;
372 use crate::unicode::Dense1x2;
373
374 let colors = [Color::Dark, Color::Light, Color::Light, Color::Dark];
375 let image = Renderer::<Dense1x2>::new(&colors, 2, 0).module_dimensions(1, 1).build();
376 assert_eq!(image, "▀▄");
377}
378
379#[test]
380fn integration_render_utf8_1x2_inverted() {
381 use crate::Renderer;
382 use crate::unicode::Dense1x2;
383
384 let colors = [Color::Dark, Color::Light, Color::Light, Color::Dark];
385 let image = Renderer::<Dense1x2>::new(&colors, 2, 0)
386 .dark_color(Dense1x2::Light)
387 .light_color(Dense1x2::Dark)
388 .module_dimensions(1, 1)
389 .build();
390 assert_eq!(image, "▄▀");
391}
392
393#[test]
394fn test_dense2x2_basic() {
395 use crate::Renderer;
396 let colors = &[Color::Dark, Color::Light, Color::Light, Color::Dark];
397 let image: String = Renderer::<Dense2x2>::new(colors, 2, 0).module_dimensions(1, 1).build();
398 assert_eq!(&image, "\u{259A}");
399}
400
401#[test]
402fn test_dense2x2_with_quiet_zone() {
403 use crate::Renderer;
404 let colors = &[Color::Dark, Color::Light, Color::Light, Color::Dark];
405 let image: String = Renderer::<Dense2x2>::new(colors, 2, 1).build();
406 assert!(image.chars().count() >= 1);
407}
408
409#[test]
410fn test_dense2x2_all_dark() {
411 use crate::Renderer;
412 let colors = vec![Color::Dark; 4];
413 let image: String = Renderer::<Dense2x2>::new(&colors, 2, 0).module_dimensions(1, 1).build();
414 assert_eq!(&image, "\u{2588}");
415}
416
417#[test]
418fn test_dense2x2_all_light() {
419 use crate::Renderer;
420 let colors = vec![Color::Light; 4];
421 let image: String = Renderer::<Dense2x2>::new(&colors, 2, 0).module_dimensions(1, 1).build();
422 assert_eq!(&image, " ");
423}
424
425#[test]
426fn integration_render_utf8_2x2() {
427 use crate::Renderer;
428 use crate::unicode::Dense2x2;
429
430 let colors = vec![
431 Color::Dark,
432 Color::Light,
433 Color::Light,
434 Color::Dark,
435 Color::Light,
436 Color::Dark,
437 Color::Dark,
438 Color::Light,
439 Color::Dark,
440 Color::Dark,
441 Color::Light,
442 Color::Light,
443 Color::Light,
444 Color::Light,
445 Color::Dark,
446 Color::Dark,
447 ];
448 let image = Renderer::<Dense2x2>::new(&colors, 4, 0).module_dimensions(1, 1).build();
449 assert!(!image.is_empty());
450 let dense1x2 = Renderer::<Dense1x2>::new(&colors, 4, 0).module_dimensions(1, 1).build();
451 assert!(image.len() < dense1x2.len());
452}
453
454#[test]
455fn test_braille_all_dark() {
456 use crate::Renderer;
457 let colors = vec![Color::Dark; 16];
458 let image: String = Renderer::<Braille>::new(&colors, 4, 0).module_dimensions(1, 1).build();
459 assert_eq!(&image, "\u{28FF}\u{28FF}");
460}
461
462#[test]
463fn test_braille_all_light() {
464 use crate::Renderer;
465 let colors = vec![Color::Light; 16];
466 let image: String = Renderer::<Braille>::new(&colors, 4, 0).module_dimensions(1, 1).build();
467 assert_eq!(&image, "\u{2800}\u{2800}");
468}
469
470#[test]
471fn test_braille_top_left_dot() {
472 use crate::Renderer;
473 let mut colors = vec![Color::Light; 16];
474 colors[0] = Color::Dark;
475 let image: String = Renderer::<Braille>::new(&colors, 4, 0).module_dimensions(1, 1).build();
476 assert_eq!(&image, "\u{2801}\u{2800}");
477}
478
479#[test]
480fn test_braille_density() {
481 use crate::Renderer;
482 use crate::unicode::{Braille, Dense1x2};
483
484 let colors = (0..64).map(|i| if i % 3 == 0 { Color::Dark } else { Color::Light }).collect::<Vec<_>>();
485 let braille = Renderer::<Braille>::new(&colors, 8, 0).module_dimensions(1, 1).build();
486 let dense1x2 = Renderer::<Dense1x2>::new(&colors, 8, 0).module_dimensions(1, 1).build();
487 assert!(braille.len() < dense1x2.len());
488}
489
490#[test]
491fn test_dense3x2_all_dark() {
492 use crate::Renderer;
493 let colors = vec![Color::Dark; 36];
495 let image: String = Renderer::<Dense3x2>::new(&colors, 6, 0).module_dimensions(1, 1).build();
496 assert_eq!(&image, "\u{1FB3F}\u{1FB3F}\u{1FB3F}\n\u{1FB3F}\u{1FB3F}\u{1FB3F}");
497}
498
499#[test]
500fn test_dense3x2_all_light() {
501 use crate::Renderer;
502 let colors = vec![Color::Light; 36];
503 let image: String = Renderer::<Dense3x2>::new(&colors, 6, 0).module_dimensions(1, 1).build();
504 assert_eq!(&image, " \n ");
505}
506
507#[test]
508fn test_dense3x2_top_left_cell() {
509 use crate::Renderer;
510 let mut colors = vec![Color::Light; 36];
512 colors[0] = Color::Dark;
513 let image: String = Renderer::<Dense3x2>::new(&colors, 6, 0).module_dimensions(1, 1).build();
514 assert!(image.starts_with('\u{1FB01}'));
516}
517
518#[test]
519fn test_dense3x2_density() {
520 use crate::Renderer;
521 use crate::unicode::{Dense1x2, Dense3x2};
522
523 let colors = (0..36).map(|i| if i % 2 == 0 { Color::Dark } else { Color::Light }).collect::<Vec<_>>();
524 let sextant = Renderer::<Dense3x2>::new(&colors, 6, 0).module_dimensions(1, 1).build();
525 let dense1x2 = Renderer::<Dense1x2>::new(&colors, 6, 0).module_dimensions(1, 1).build();
526 assert!(sextant.len() < dense1x2.len());
528}