1use std::hash::{
2 Hash,
3 Hasher,
4};
5
6use alacritty_terminal::{
7 selection::SelectionRange,
8 term::cell::{
9 Cell,
10 Flags,
11 },
12};
13use freya_core::{
14 fifo_cache::FifoCache,
15 prelude::Color,
16};
17use freya_engine::prelude::{
18 Canvas,
19 Font,
20 FontCollection,
21 Paint,
22 Paragraph,
23 ParagraphBuilder,
24 ParagraphStyle,
25 SkRect,
26 TextBlob,
27 TextStyle,
28};
29use rustc_hash::FxHasher;
30use torin::prelude::Area;
31
32use crate::colors::map_ansi_color;
33
34pub(crate) enum CachedRow {
36 TextBlobs(Vec<(TextBlob, Color)>),
38 Paragraph(Paragraph),
40}
41
42pub(crate) struct Renderer<'a> {
45 pub canvas: &'a Canvas,
46 pub paint: &'a mut Paint,
47 pub font: &'a Font,
48 pub font_collection: &'a mut FontCollection,
49 pub row_cache: &'a mut FifoCache<u64, CachedRow>,
50 pub area: Area,
51 pub char_width: f32,
52 pub line_height: f32,
53 pub baseline_offset: f32,
54 pub foreground: Color,
55 pub background: Color,
56 pub selection_color: Color,
57 pub font_family: &'a str,
58 pub font_size: f32,
59 pub selection: Option<SelectionRange>,
60 pub display_offset: usize,
61}
62
63impl Renderer<'_> {
64 pub fn render_background(&mut self) {
65 self.paint.set_color(self.background);
66 self.canvas.draw_rect(
67 SkRect::new(
68 self.area.min_x(),
69 self.area.min_y(),
70 self.area.max_x(),
71 self.area.max_y(),
72 ),
73 self.paint,
74 );
75 }
76
77 pub fn render_row(&mut self, row_idx: usize, row: &[Cell], y: f32) {
79 self.render_cell_backgrounds(row, y);
80 self.render_text_row(row, y);
81 self.render_selection(row_idx, row.len(), y);
82 }
83
84 pub fn render_cursor(&mut self, cell: &Cell, y: f32, cursor_col: usize) {
85 let left = self.area.min_x() + (cursor_col as f32) * self.char_width;
86 let right = left + self.char_width.max(1.0);
87 let bottom = y + self.line_height.max(1.0);
88
89 self.paint.set_color(self.foreground);
90 self.canvas.draw_rect(
91 SkRect::new(left, y.round(), right, bottom.round()),
92 self.paint,
93 );
94
95 let glyph = match cell.c {
96 '\0' | '\t' => ' ',
97 c => c,
98 };
99 let mut buf = [0u8; 4];
100 let content: &str = glyph.encode_utf8(&mut buf);
101
102 self.paint.set_color(self.background);
103 if let Some(blob) = TextBlob::from_pos_text_h(content, &[0.0], 0.0, self.font) {
104 self.canvas
105 .draw_text_blob(&blob, (left, y + self.baseline_offset), self.paint);
106 }
107 }
108
109 pub fn render_scrollbar(
110 &mut self,
111 scroll_offset: usize,
112 total_scrollback: usize,
113 rows_count: usize,
114 ) {
115 let viewport_height = self.area.height();
116 let total_rows = rows_count + total_scrollback;
117 let total_content_height = total_rows as f32 * self.line_height;
118
119 let scrollbar_height = (viewport_height * viewport_height / total_content_height).max(20.0);
120 let track_height = viewport_height - scrollbar_height;
121 let scroll_ratio = scroll_offset as f32 / total_scrollback as f32;
122 let thumb_y = self.area.min_y() + track_height * (1.0 - scroll_ratio);
123
124 let scrollbar_x = self.area.max_x() - 4.0;
125 let corner_radius = 2.0;
126
127 self.paint.set_anti_alias(true);
128 self.paint.set_color(Color::from_argb(50, 0, 0, 0));
129 self.canvas.draw_round_rect(
130 SkRect::new(
131 scrollbar_x,
132 self.area.min_y(),
133 self.area.max_x(),
134 self.area.max_y(),
135 ),
136 corner_radius,
137 corner_radius,
138 self.paint,
139 );
140
141 self.paint.set_color(Color::from_argb(60, 255, 255, 255));
142 self.canvas.draw_round_rect(
143 SkRect::new(
144 scrollbar_x,
145 thumb_y,
146 self.area.max_x(),
147 thumb_y + scrollbar_height,
148 ),
149 corner_radius,
150 corner_radius,
151 self.paint,
152 );
153 }
154
155 fn render_cell_backgrounds(&mut self, row: &[Cell], y: f32) {
156 let mut run_start: Option<(usize, Color)> = None;
157 let mut col = 0;
158 while col < row.len() {
159 let cell = &row[col];
160 if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
161 col += 1;
162 continue;
163 }
164 let cell_bg = if cell.flags.contains(Flags::INVERSE) {
165 map_ansi_color(cell.fg, self.foreground, self.background)
166 } else {
167 map_ansi_color(cell.bg, self.foreground, self.background)
168 };
169 let end_col = if cell.flags.contains(Flags::WIDE_CHAR) {
170 col + 2
171 } else {
172 col + 1
173 };
174
175 if cell_bg != self.background {
176 match &run_start {
177 Some((_, color)) if *color == cell_bg => {}
178 Some((start, color)) => {
179 self.fill_cells(*start, col, *color, y);
180 run_start = Some((col, cell_bg));
181 }
182 None => {
183 run_start = Some((col, cell_bg));
184 }
185 }
186 } else if let Some((start, color)) = run_start.take() {
187 self.fill_cells(start, col, color, y);
188 }
189 col = end_col;
190 }
191 if let Some((start, color)) = run_start {
192 self.fill_cells(start, col, color, y);
193 }
194 }
195
196 fn fill_cells(&mut self, start: usize, end: usize, color: Color, y: f32) {
197 let left = self.area.min_x() + (start as f32) * self.char_width;
198 let right = self.area.min_x() + (end as f32) * self.char_width;
199 self.paint.set_color(color);
200 self.canvas.draw_rect(
201 SkRect::new(left, y.round(), right, (y + self.line_height).round()),
202 self.paint,
203 );
204 }
205
206 fn render_selection(&mut self, row_idx: usize, row_len: usize, y: f32) {
207 let Some(range) = self.selection else {
208 return;
209 };
210 let offset = self.display_offset as i64;
211 let start_row = range.start.line.0 as i64 + offset;
212 let end_row = range.end.line.0 as i64 + offset;
213 let row_i = row_idx as i64;
214 if row_i < start_row || row_i > end_row {
215 return;
216 }
217 let sel_start = if row_i == start_row {
218 range.start.column.0
219 } else {
220 0
221 };
222 let sel_end = if row_i == end_row {
223 (range.end.column.0 + 1).min(row_len)
224 } else {
225 row_len
226 };
227 if sel_start >= sel_end {
228 return;
229 }
230 let left = self.area.min_x() + (sel_start as f32) * self.char_width;
231 let right = self.area.min_x() + (sel_end as f32) * self.char_width;
232 self.paint.set_color(self.selection_color);
233 self.canvas.draw_rect(
234 SkRect::new(left, y.round(), right, (y + self.line_height).round()),
235 self.paint,
236 );
237 }
238
239 fn render_text_row(&mut self, row: &[Cell], y: f32) {
243 let mut hasher = FxHasher::default();
244 let mut needs_fallback = false;
245 for cell in row.iter() {
246 if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
247 continue;
248 }
249 let text = cell_text(cell);
250 let cell_fg = self.cell_foreground(cell);
251 text.hash(&mut hasher);
252 cell_fg.hash(&mut hasher);
253 if !needs_fallback {
254 needs_fallback = cell.flags.contains(Flags::WIDE_CHAR)
255 || (!text.is_ascii() && self.font.text_to_glyphs_vec(&text).contains(&0));
256 }
257 }
258 let cache_key = hasher.finish();
259 let text_y = y + self.baseline_offset;
260 let area_min_x = self.area.min_x();
261
262 if let Some(cached) = self.row_cache.get(&cache_key) {
263 match cached {
264 CachedRow::TextBlobs(blobs) => {
265 for (blob, color) in blobs {
266 self.paint.set_color(*color);
267 self.canvas
268 .draw_text_blob(blob, (area_min_x, text_y), self.paint);
269 }
270 }
271 CachedRow::Paragraph(paragraph) => {
272 paragraph.paint(self.canvas, (area_min_x, y));
273 }
274 }
275 } else if needs_fallback {
276 self.render_paragraph(row, y, cache_key);
277 } else {
278 self.render_textblob(row, text_y, cache_key);
279 }
280 }
281
282 fn cell_foreground(&self, cell: &Cell) -> Color {
283 let raw = if cell.flags.contains(Flags::INVERSE) {
284 cell.bg
285 } else {
286 cell.fg
287 };
288 map_ansi_color(raw, self.foreground, self.background)
289 }
290
291 fn render_textblob(&mut self, row: &[Cell], text_y: f32, cache_key: u64) {
294 let mut current_color: Option<Color> = None;
295 let mut glyphs = String::new();
296 let mut glyph_positions: Vec<f32> = Vec::new();
297 let mut blobs: Vec<(TextBlob, Color)> = Vec::new();
298
299 for (col_idx, cell) in row.iter().enumerate() {
300 if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
301 continue;
302 }
303 let cell_fg = self.cell_foreground(cell);
304 let text = cell_text(cell);
305 let x = (col_idx as f32) * self.char_width;
306
307 if current_color != Some(cell_fg) {
308 if let Some(prev_color) = current_color {
309 self.flush_blob(&glyphs, &glyph_positions, text_y, &mut blobs, prev_color);
310 glyphs.clear();
311 glyph_positions.clear();
312 }
313 current_color = Some(cell_fg);
314 }
315 for _ in text.chars() {
316 glyph_positions.push(x);
317 }
318 glyphs.push_str(&text);
319 }
320
321 if let Some(color) = current_color
322 && !glyphs.is_empty()
323 {
324 self.flush_blob(&glyphs, &glyph_positions, text_y, &mut blobs, color);
325 }
326
327 self.row_cache
328 .insert(cache_key, CachedRow::TextBlobs(blobs));
329 }
330
331 fn flush_blob(
332 &mut self,
333 glyphs: &str,
334 glyph_positions: &[f32],
335 text_y: f32,
336 blobs: &mut Vec<(TextBlob, Color)>,
337 color: Color,
338 ) {
339 if let Some(blob) = TextBlob::from_pos_text_h(glyphs, glyph_positions, 0.0, self.font) {
340 self.paint.set_color(color);
341 self.canvas
342 .draw_text_blob(&blob, (self.area.min_x(), text_y), self.paint);
343 blobs.push((blob, color));
344 }
345 }
346
347 fn render_paragraph(&mut self, row: &[Cell], row_y: f32, cache_key: u64) {
349 let mut text_style = TextStyle::new();
350 text_style.set_font_size(self.font_size);
351 text_style.set_font_families(&[self.font_family]);
352 text_style.set_color(self.foreground);
353
354 let mut builder =
355 ParagraphBuilder::new(&ParagraphStyle::default(), self.font_collection.clone());
356
357 for cell in row.iter() {
358 if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
359 continue;
360 }
361 let mut cell_style = text_style.clone();
362 cell_style.set_color(self.cell_foreground(cell));
363 builder.push_style(&cell_style);
364 builder.add_text(cell_text(cell).as_str());
365 }
366
367 let mut paragraph = builder.build();
368 paragraph.layout(f32::MAX);
369 paragraph.paint(self.canvas, (self.area.min_x(), row_y));
370
371 self.row_cache
372 .insert(cache_key, CachedRow::Paragraph(paragraph));
373 }
374}
375
376fn cell_text(cell: &Cell) -> String {
378 let mut s = String::new();
379 s.push(match cell.c {
380 '\0' | '\t' => ' ',
381 c => c,
382 });
383 if let Some(extra) = cell.zerowidth() {
384 for c in extra {
385 s.push(*c);
386 }
387 }
388 s
389}