1use alloc::collections::btree_map::BTreeMap;
2use core::mem::swap;
3use derive_more::{Deref, DerefMut};
4
5use crate::cell::{Cell, Flags};
6use crate::color::Rgb;
7use crate::config::CONFIG;
8use crate::font::{ContentInfo, Rasterized};
9
10pub trait DrawTarget {
11 fn size(&self) -> (usize, usize);
12 fn draw_pixel(&mut self, x: usize, y: usize, color: Rgb);
13}
14
15#[derive(Deref, DerefMut)]
16pub struct Graphic<D: DrawTarget> {
17 #[deref]
18 #[deref_mut]
19 graphic: D,
20 color_cache: BTreeMap<(Rgb, Rgb), ColorCache>,
21}
22
23impl<D: DrawTarget> Graphic<D> {
24 pub fn new(graphic: D) -> Self {
25 Self {
26 graphic,
27 color_cache: BTreeMap::new(),
28 }
29 }
30
31 pub fn clear(&mut self, cell: Cell) {
32 let color = cell.background.to_rgb();
33
34 for y in 0..self.graphic.size().1 {
35 for x in 0..self.graphic.size().0 {
36 self.graphic.draw_pixel(x, y, color);
37 }
38 }
39 }
40
41 pub fn write(&mut self, row: usize, col: usize, cell: Cell) {
42 if cell.placeholder {
43 return;
44 }
45
46 let mut foreground = cell.foreground.to_rgb();
47 let mut background = cell.background.to_rgb();
48
49 if cell.flags.intersects(Flags::INVERSE | Flags::CURSOR_BLOCK) {
50 swap(&mut foreground, &mut background);
51 }
52
53 if cell.flags.contains(Flags::HIDDEN) {
54 foreground = background;
55 }
56
57 if let Some(font_manager) = CONFIG.font_manager.lock().as_mut() {
58 let (font_width, font_height) = font_manager.size();
59 let (x_start, y_start) = (col * font_width, row * font_height);
60
61 let color_cache = self
62 .color_cache
63 .entry((foreground, background))
64 .or_insert_with(|| ColorCache::new(foreground, background));
65
66 let content_info = ContentInfo {
67 content: cell.content,
68 bold: cell.flags.contains(Flags::BOLD),
69 italic: cell.flags.contains(Flags::ITALIC),
70 wide: cell.wide,
71 };
72
73 macro_rules! draw_raster {
74 ($raster:ident) => {
75 for (y, lines) in $raster.iter().enumerate() {
76 for (x, &intensity) in lines.iter().enumerate() {
77 let (r, g, b) = color_cache.colors[intensity as usize];
78 self.graphic.draw_pixel(x_start + x, y_start + y, (r, g, b));
79 }
80 }
81 };
82 }
83
84 match font_manager.rasterize(content_info) {
85 Rasterized::Slice(raster) => draw_raster!(raster),
86 Rasterized::Vec(raster) => draw_raster!(raster),
87 Rasterized::Owned(raster) => draw_raster!(raster),
88 }
89
90 if cell.flags.contains(Flags::CURSOR_BEAM) {
91 let (r, g, b) = color_cache.colors[0xff];
92 (0..font_height)
93 .for_each(|y| self.graphic.draw_pixel(x_start, y_start + y, (r, g, b)));
94 }
95
96 if cell
97 .flags
98 .intersects(Flags::UNDERLINE | Flags::CURSOR_UNDERLINE)
99 {
100 let (r, g, b) = color_cache.colors[0xff];
101 let y_base = y_start + font_height - 1;
102 (0..font_width)
103 .for_each(|x| self.graphic.draw_pixel(x_start + x, y_base, (r, g, b)));
104 }
105 }
106 }
107}
108
109struct ColorCache {
110 colors: [Rgb; 256],
111}
112
113impl ColorCache {
114 fn new(foreground: Rgb, background: Rgb) -> Self {
115 let [r_diff, g_diff, b_diff] = [
116 foreground.0 as i32 - background.0 as i32,
117 foreground.1 as i32 - background.1 as i32,
118 foreground.2 as i32 - background.2 as i32,
119 ];
120
121 let colors = core::array::from_fn(|intensity| {
122 let weight = intensity as i32;
123 (
124 ((background.0 as i32 + (r_diff * weight / 0xff)).clamp(0, 255)) as u8,
125 ((background.1 as i32 + (g_diff * weight / 0xff)).clamp(0, 255)) as u8,
126 ((background.2 as i32 + (b_diff * weight / 0xff)).clamp(0, 255)) as u8,
127 )
128 });
129
130 Self { colors }
131 }
132}