os_terminal/
graphic.rs

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, ToRgb};
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
42impl<D: DrawTarget> Graphic<D> {
43    pub fn write(&mut self, row: usize, col: usize, cell: Cell) {
44        if cell.placeholder {
45            return;
46        }
47
48        let mut foreground = cell.foreground.to_rgb();
49        let mut background = cell.background.to_rgb();
50
51        if cell.flags.intersects(Flags::INVERSE | Flags::CURSOR_BLOCK) {
52            swap(&mut foreground, &mut background);
53        }
54
55        if cell.flags.contains(Flags::HIDDEN) {
56            foreground = background;
57        }
58
59        if let Some(font_manager) = CONFIG.font_manager.lock().as_mut() {
60            let (font_width, font_height) = font_manager.size();
61            let (x_start, y_start) = (col * font_width, row * font_height);
62
63            let color_cache = self
64                .color_cache
65                .entry((foreground, background))
66                .or_insert_with(|| ColorCache::new(foreground, background));
67
68            let content_info = ContentInfo {
69                content: cell.content,
70                bold: cell.flags.contains(Flags::BOLD),
71                italic: cell.flags.contains(Flags::ITALIC),
72                wide: cell.wide,
73            };
74
75            macro_rules! draw_raster {
76                ($raster:ident) => {
77                    for (y, lines) in $raster.iter().enumerate() {
78                        for (x, &intensity) in lines.iter().enumerate() {
79                            let (r, g, b) = color_cache[intensity as usize];
80                            self.graphic.draw_pixel(x_start + x, y_start + y, (r, g, b));
81                        }
82                    }
83                };
84            }
85
86            match font_manager.rasterize(content_info) {
87                Rasterized::Slice(raster) => draw_raster!(raster),
88                Rasterized::Vec(raster) => draw_raster!(raster),
89                Rasterized::Owned(raster) => draw_raster!(raster),
90            }
91
92            if cell.flags.contains(Flags::CURSOR_BEAM) {
93                let (r, g, b) = color_cache[0xff];
94                (0..font_height)
95                    .for_each(|y| self.graphic.draw_pixel(x_start, y_start + y, (r, g, b)));
96            }
97
98            if cell
99                .flags
100                .intersects(Flags::UNDERLINE | Flags::CURSOR_UNDERLINE)
101            {
102                let (r, g, b) = color_cache[0xff];
103                let y_base = y_start + font_height - 1;
104                (0..font_width)
105                    .for_each(|x| self.graphic.draw_pixel(x_start + x, y_base, (r, g, b)));
106            }
107        }
108    }
109}
110
111#[derive(Deref)]
112struct ColorCache([Rgb; 256]);
113
114impl ColorCache {
115    fn new(foreground: Rgb, background: Rgb) -> Self {
116        let (r_diff, g_diff, b_diff) = (
117            foreground.0 as i32 - background.0 as i32,
118            foreground.1 as i32 - background.1 as i32,
119            foreground.2 as i32 - background.2 as i32,
120        );
121
122        let colors = core::array::from_fn(|intensity| {
123            let weight = intensity as i32;
124            (
125                ((background.0 as i32 + (r_diff * weight / 0xff)).clamp(0, 255)) as u8,
126                ((background.1 as i32 + (g_diff * weight / 0xff)).clamp(0, 255)) as u8,
127                ((background.2 as i32 + (b_diff * weight / 0xff)).clamp(0, 255)) as u8,
128            )
129        });
130
131        Self(colors)
132    }
133}