1use alloc::boxed::Box;
2use core::mem::swap;
3use core::num::NonZeroUsize;
4use core::ops::{Deref, DerefMut};
5use lru::LruCache;
6use vte::ansi::Color;
7
8use crate::cell::{Cell, Flags};
9use crate::color::{ColorScheme, Rgb};
10use crate::font::{ContentInfo, FontManager, Rasterized};
11
12pub trait DrawTarget {
13 fn size(&self) -> (usize, usize);
14 fn draw_pixel(&mut self, x: usize, y: usize, pixel: u32);
15 fn rgb_to_pixel(&self, rgb: Rgb) -> u32;
16}
17
18pub struct Graphic<D: DrawTarget> {
19 display: D,
20 pub(crate) color_scheme: ColorScheme,
21 pub(crate) font_manager: Option<Box<dyn FontManager>>,
22 color_cache: LruCache<(Rgb, Rgb), ColorCache>,
23}
24
25impl<D: DrawTarget> Deref for Graphic<D> {
26 type Target = D;
27
28 fn deref(&self) -> &Self::Target {
29 &self.display
30 }
31}
32
33impl<D: DrawTarget> DerefMut for Graphic<D> {
34 fn deref_mut(&mut self) -> &mut Self::Target {
35 &mut self.display
36 }
37}
38
39impl<D: DrawTarget> Graphic<D> {
40 pub fn new(display: D) -> Self {
41 Self {
42 display,
43 color_scheme: ColorScheme::default(),
44 font_manager: None,
45 color_cache: LruCache::new(NonZeroUsize::new(128).unwrap()),
46 }
47 }
48
49 pub fn set_cache_size(&mut self, size: usize) {
50 assert!(size > 0, "Cache size must be greater than 0");
51 self.color_cache.resize(NonZeroUsize::new(size).unwrap());
52 }
53}
54
55impl<D: DrawTarget> Graphic<D> {
56 pub fn clear(&mut self, cell: Cell) {
57 let (width, height) = self.display.size();
58 let rgb = self.color_to_rgb(cell.background);
59 let pixel = self.display.rgb_to_pixel(rgb);
60
61 for y in 0..height {
62 for x in 0..width {
63 self.display.draw_pixel(x, y, pixel);
64 }
65 }
66 }
67
68 pub fn color_to_rgb(&self, color: Color) -> Rgb {
69 match color {
70 Color::Spec(rgb) => (rgb.r, rgb.g, rgb.b),
71 Color::Named(color) => match color as usize {
72 256 => self.color_scheme.foreground,
73 257 => self.color_scheme.background,
74 index => self.color_scheme.ansi_colors[index],
75 },
76 Color::Indexed(index) => {
77 let color_scheme = &self.color_scheme;
78 color_scheme.ansi_colors[index as usize]
79 }
80 }
81 }
82}
83
84impl<D: DrawTarget> Graphic<D> {
85 pub fn write(&mut self, row: usize, col: usize, cell: Cell) {
86 if cell.placeholder {
87 return;
88 }
89
90 let mut foreground = self.color_to_rgb(cell.foreground);
91 let mut background = self.color_to_rgb(cell.background);
92
93 if cell.flags.intersects(Flags::INVERSE | Flags::CURSOR_BLOCK) {
94 swap(&mut foreground, &mut background);
95 }
96
97 if cell.flags.contains(Flags::HIDDEN) {
98 foreground = background;
99 }
100
101 if let Some(font_manager) = self.font_manager.as_mut() {
102 let (font_width, font_height) = font_manager.size();
103 let (x_start, y_start) = (col * font_width, row * font_height);
104
105 let color_cache = self
106 .color_cache
107 .get_or_insert((foreground, background), || {
108 ColorCache::new(foreground, background, &self.display)
109 });
110
111 let content_info = ContentInfo {
112 content: cell.content,
113 bold: cell.flags.contains(Flags::BOLD),
114 italic: cell.flags.contains(Flags::ITALIC),
115 wide: cell.wide,
116 };
117
118 macro_rules! draw_raster {
119 ($raster:ident) => {
120 for (y, lines) in $raster.iter().enumerate() {
121 for (x, &intensity) in lines.iter().enumerate() {
122 let pixel = color_cache.0[intensity as usize];
123 self.display.draw_pixel(x_start + x, y_start + y, pixel);
124 }
125 }
126 };
127 }
128
129 match font_manager.rasterize(content_info) {
130 Rasterized::Slice(raster) => draw_raster!(raster),
131 Rasterized::Vec(raster) => draw_raster!(raster),
132 Rasterized::Owned(raster) => draw_raster!(raster),
133 }
134
135 if cell.flags.contains(Flags::CURSOR_BEAM) {
136 let pixel = color_cache.0[0xff];
137 (0..font_height)
138 .for_each(|y| self.display.draw_pixel(x_start, y_start + y, pixel));
139 }
140
141 if cell
142 .flags
143 .intersects(Flags::UNDERLINE | Flags::CURSOR_UNDERLINE)
144 {
145 let pixel = color_cache.0[0xff];
146 let y_base = y_start + font_height - 1;
147 (0..font_width)
148 .for_each(|x| self.display.draw_pixel(x_start + x, y_base, pixel));
149 }
150 }
151 }
152}
153
154struct ColorCache([u32; 256]);
155
156impl ColorCache {
157 fn new<D: DrawTarget>(foreground: Rgb, background: Rgb, display: &D) -> Self {
158 let (r_diff, g_diff, b_diff) = (
159 foreground.0 as i32 - background.0 as i32,
160 foreground.1 as i32 - background.1 as i32,
161 foreground.2 as i32 - background.2 as i32,
162 );
163
164 let colors = core::array::from_fn(|intensity| {
165 let weight = intensity as i32;
166
167 let r = ((background.0 as i32 + (r_diff * weight / 0xff)).clamp(0, 255)) as u8;
168 let g = ((background.1 as i32 + (g_diff * weight / 0xff)).clamp(0, 255)) as u8;
169 let b = ((background.2 as i32 + (b_diff * weight / 0xff)).clamp(0, 255)) as u8;
170
171 display.rgb_to_pixel((r, g, b))
172 });
173
174 Self(colors)
175 }
176}