1use std::hash::{
2 Hash,
3 Hasher,
4};
5
6use freya_core::{
7 fifo_cache::FifoCache,
8 prelude::Color,
9};
10use freya_engine::prelude::{
11 Canvas,
12 Font,
13 FontCollection,
14 Paint,
15 Paragraph,
16 ParagraphBuilder,
17 ParagraphStyle,
18 TextBlob,
19 TextStyle,
20};
21use rustc_hash::FxHasher;
22
23use crate::colors::map_vt100_color;
24
25pub(crate) enum CachedRow {
26 TextBlobs(Vec<(TextBlob, Color)>),
27 Paragraph(Paragraph),
28}
29
30pub(crate) struct TextRenderer<'a> {
32 pub canvas: &'a Canvas,
33 pub font: &'a Font,
34 pub font_collection: &'a mut FontCollection,
35 pub paint: &'a mut Paint,
36 pub row_cache: &'a mut FifoCache<u64, CachedRow>,
37 pub area_min_x: f32,
38 pub char_width: f32,
39 pub line_height: f32,
40 pub baseline_offset: f32,
41 pub foreground: Color,
42 pub background: Color,
43 pub font_family: &'a str,
44 pub font_size: f32,
45}
46
47impl TextRenderer<'_> {
48 fn cell_text(cell: &vt100::Cell) -> &str {
49 if cell.has_contents() {
50 cell.contents()
51 } else {
52 " "
53 }
54 }
55
56 fn cell_foreground(&self, cell: &vt100::Cell) -> Color {
57 if cell.inverse() {
58 map_vt100_color(cell.bgcolor(), self.background)
59 } else {
60 map_vt100_color(cell.fgcolor(), self.foreground)
61 }
62 }
63
64 fn render_blob(
65 &mut self,
66 glyphs: &str,
67 glyph_positions: &[f32],
68 text_y: f32,
69 blobs: &mut Vec<(TextBlob, Color)>,
70 color: Color,
71 ) {
72 if let Some(blob) = TextBlob::from_pos_text_h(glyphs, glyph_positions, 0.0, self.font) {
73 self.paint.set_color(color);
74 self.canvas
75 .draw_text_blob(&blob, (self.area_min_x, text_y), self.paint);
76 blobs.push((blob, color));
77 }
78 }
79
80 pub fn render_text(
81 &mut self,
82 rows: &[Vec<vt100::Cell>],
83 area_min_y: f32,
84 area_max_y: f32,
85 mut pre_row: impl FnMut(&[vt100::Cell], f32, &Canvas, &mut Paint),
86 mut post_row: impl FnMut(usize, &[vt100::Cell], f32, &Canvas, &mut Paint),
87 ) {
88 let mut y = area_min_y;
89
90 for (row_idx, row) in rows.iter().enumerate() {
91 if y + self.line_height > area_max_y {
92 break;
93 }
94
95 pre_row(row, y, self.canvas, self.paint);
96
97 let mut hasher = FxHasher::default();
98 let mut needs_fallback = false;
99 for cell in row.iter() {
100 if cell.is_wide_continuation() {
101 continue;
102 }
103 let contents = cell.contents();
104 let cell_fg = self.cell_foreground(cell);
105 contents.hash(&mut hasher);
106 cell_fg.hash(&mut hasher);
107 if !needs_fallback {
108 needs_fallback = cell.is_wide()
109 || (!contents.is_ascii()
110 && self.font.text_to_glyphs_vec(contents).contains(&0));
111 }
112 }
113 let cache_key = hasher.finish();
114 let text_y = y + self.baseline_offset;
115
116 if let Some(cached) = self.row_cache.get(&cache_key) {
117 match cached {
118 CachedRow::TextBlobs(blobs) => {
119 for (blob, color) in blobs {
120 self.paint.set_color(*color);
121 self.canvas
122 .draw_text_blob(blob, (self.area_min_x, text_y), self.paint);
123 }
124 }
125 CachedRow::Paragraph(paragraph) => {
126 paragraph.paint(self.canvas, (self.area_min_x, y));
127 }
128 }
129 } else if needs_fallback {
130 self.render_paragraph(row, y, cache_key);
131 } else {
132 self.render_textblob(row, text_y, cache_key);
133 }
134
135 post_row(row_idx, row, y, self.canvas, self.paint);
136
137 y += self.line_height;
138 }
139 }
140
141 fn render_textblob(&mut self, row: &[vt100::Cell], text_y: f32, cache_key: u64) {
143 let mut current_color: Option<Color> = None;
144
145 let mut glyphs = String::new();
148 let mut glyph_positions: Vec<f32> = Vec::new();
149
150 let mut blobs: Vec<(TextBlob, Color)> = Vec::new();
151
152 for (col_idx, cell) in row.iter().enumerate() {
153 if cell.is_wide_continuation() {
154 continue;
155 }
156 let cell_fg = self.cell_foreground(cell);
157 let text = Self::cell_text(cell);
158 let x = (col_idx as f32) * self.char_width;
159
160 if current_color != Some(cell_fg) {
161 if let Some(prev_color) = current_color {
162 self.render_blob(&glyphs, &glyph_positions, text_y, &mut blobs, prev_color);
163 glyphs.clear();
164 glyph_positions.clear();
165 }
166 current_color = Some(cell_fg);
167 }
168 for _ in text.chars() {
169 glyph_positions.push(x);
170 }
171 glyphs.push_str(text);
172 }
173
174 if !glyphs.is_empty() {
175 self.render_blob(
176 &glyphs,
177 &glyph_positions,
178 text_y,
179 &mut blobs,
180 current_color.unwrap(),
181 );
182 }
183
184 self.row_cache
185 .insert(cache_key, CachedRow::TextBlobs(blobs));
186 }
187
188 fn render_paragraph(&mut self, row: &[vt100::Cell], row_y: f32, cache_key: u64) {
190 let mut text_style = TextStyle::new();
191 text_style.set_font_size(self.font_size);
192 text_style.set_font_families(&[self.font_family]);
193 text_style.set_color(self.foreground);
194
195 let mut builder =
196 ParagraphBuilder::new(&ParagraphStyle::default(), self.font_collection.clone());
197
198 for cell in row.iter() {
199 if cell.is_wide_continuation() {
200 continue;
201 }
202 let mut cell_style = text_style.clone();
203 cell_style.set_color(self.cell_foreground(cell));
204 builder.push_style(&cell_style);
205 builder.add_text(Self::cell_text(cell));
206 }
207
208 let mut paragraph = builder.build();
209 paragraph.layout(f32::MAX);
210 paragraph.paint(self.canvas, (self.area_min_x, row_y));
211
212 self.row_cache
213 .insert(cache_key, CachedRow::Paragraph(paragraph));
214 }
215}