par_term_render/cell_renderer/
atlas.rs1use super::{CellRenderer, GlyphInfo};
2
3pub(crate) struct RasterizedGlyph {
4 pub width: u32,
5 pub height: u32,
6 #[allow(dead_code)]
7 pub bearing_x: f32,
8 #[allow(dead_code)]
9 pub bearing_y: f32,
10 pub pixels: Vec<u8>,
11 pub is_colored: bool,
12}
13
14pub mod symbol_ranges {
19 pub const DINGBATS_START: u32 = 0x2700;
22 pub const DINGBATS_END: u32 = 0x27BF;
23
24 pub const MISC_SYMBOLS_START: u32 = 0x2600;
27 pub const MISC_SYMBOLS_END: u32 = 0x26FF;
28
29 pub const MISC_SYMBOLS_ARROWS_START: u32 = 0x2B00;
32 pub const MISC_SYMBOLS_ARROWS_END: u32 = 0x2BFF;
33}
34
35pub fn should_render_as_symbol(ch: char) -> bool {
44 let code = ch as u32;
45
46 if (symbol_ranges::DINGBATS_START..=symbol_ranges::DINGBATS_END).contains(&code) {
48 return true;
49 }
50
51 if (symbol_ranges::MISC_SYMBOLS_START..=symbol_ranges::MISC_SYMBOLS_END).contains(&code) {
53 return true;
54 }
55
56 if (symbol_ranges::MISC_SYMBOLS_ARROWS_START..=symbol_ranges::MISC_SYMBOLS_ARROWS_END)
58 .contains(&code)
59 {
60 return true;
61 }
62
63 false
64}
65
66impl CellRenderer {
67 pub fn clear_glyph_cache(&mut self) {
68 self.glyph_cache.clear();
69 self.lru_head = None;
70 self.lru_tail = None;
71 self.atlas_next_x = 0;
72 self.atlas_next_y = 0;
73 self.atlas_row_height = 0;
74 self.dirty_rows.fill(true);
75 self.upload_solid_pixel();
77 }
78
79 pub(crate) fn lru_remove(&mut self, key: u64) {
80 let info = self.glyph_cache.get(&key).unwrap();
81 let prev = info.prev;
82 let next = info.next;
83
84 if let Some(p) = prev {
85 self.glyph_cache.get_mut(&p).unwrap().next = next;
86 } else {
87 self.lru_head = next;
88 }
89
90 if let Some(n) = next {
91 self.glyph_cache.get_mut(&n).unwrap().prev = prev;
92 } else {
93 self.lru_tail = prev;
94 }
95 }
96
97 pub(crate) fn lru_push_front(&mut self, key: u64) {
98 let next = self.lru_head;
99 if let Some(n) = next {
100 self.glyph_cache.get_mut(&n).unwrap().prev = Some(key);
101 } else {
102 self.lru_tail = Some(key);
103 }
104
105 let info = self.glyph_cache.get_mut(&key).unwrap();
106 info.prev = None;
107 info.next = next;
108 self.lru_head = Some(key);
109 }
110
111 pub(crate) fn rasterize_glyph(
112 &self,
113 font_idx: usize,
114 glyph_id: u16,
115 force_monochrome: bool,
116 ) -> Option<RasterizedGlyph> {
117 let font = self.font_manager.get_font(font_idx)?;
118 use swash::scale::image::Content;
120 use swash::scale::{Render, ScaleContext};
121 use swash::zeno::Format;
122
123 let mut context = ScaleContext::new();
124
125 let mut scaler = context
127 .builder(*font)
128 .size(self.font_size_pixels)
129 .hint(self.font_hinting)
130 .build();
131
132 let use_thin_strokes = self.should_use_thin_strokes();
134 let render_format = if !self.font_antialias {
135 Format::Alpha
137 } else if use_thin_strokes {
138 Format::Subpixel
140 } else {
141 Format::Alpha
143 };
144
145 let sources = if force_monochrome {
149 [
153 swash::scale::Source::Outline,
154 swash::scale::Source::ColorOutline(0),
155 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
156 ]
157 } else {
158 [
160 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
161 swash::scale::Source::ColorOutline(0),
162 swash::scale::Source::Outline,
163 ]
164 };
165
166 let image = Render::new(&sources)
167 .format(render_format)
168 .render(&mut scaler, glyph_id)?;
169
170 let (pixels, is_colored) = match image.content {
171 Content::Color => {
172 if force_monochrome {
176 let pixels = convert_color_to_alpha_mask(&image);
177 (pixels, false)
178 } else {
179 (image.data.clone(), true)
180 }
181 }
182 Content::Mask => {
183 let mut pixels = Vec::with_capacity(image.data.len() * 4);
184 for &mask in &image.data {
185 let alpha = if !self.font_antialias {
187 if mask > 127 { 255 } else { 0 }
188 } else {
189 mask
190 };
191 pixels.extend_from_slice(&[255, 255, 255, alpha]);
192 }
193 (pixels, false)
194 }
195 Content::SubpixelMask => {
196 let pixels = convert_subpixel_mask_to_rgba(&image);
197 (pixels, false)
198 }
199 };
200
201 Some(RasterizedGlyph {
202 width: image.placement.width,
203 height: image.placement.height,
204 bearing_x: image.placement.left as f32,
205 bearing_y: image.placement.top as f32,
206 pixels,
207 is_colored,
208 })
209 }
210
211 pub(crate) fn upload_glyph(&mut self, _key: u64, raster: &RasterizedGlyph) -> GlyphInfo {
212 let padding = 2;
213 if self.atlas_next_x + raster.width + padding > 2048 {
214 self.atlas_next_x = 0;
215 self.atlas_next_y += self.atlas_row_height + padding;
216 self.atlas_row_height = 0;
217 }
218
219 if self.atlas_next_y + raster.height + padding > 2048 {
220 self.clear_glyph_cache();
221 }
222
223 let info = GlyphInfo {
224 key: _key,
225 x: self.atlas_next_x,
226 y: self.atlas_next_y,
227 width: raster.width,
228 height: raster.height,
229 bearing_x: raster.bearing_x,
230 bearing_y: raster.bearing_y,
231 is_colored: raster.is_colored,
232 prev: None,
233 next: None,
234 };
235
236 self.queue.write_texture(
237 wgpu::TexelCopyTextureInfo {
238 texture: &self.atlas_texture,
239 mip_level: 0,
240 origin: wgpu::Origin3d {
241 x: info.x,
242 y: info.y,
243 z: 0,
244 },
245 aspect: wgpu::TextureAspect::All,
246 },
247 &raster.pixels,
248 wgpu::TexelCopyBufferLayout {
249 offset: 0,
250 bytes_per_row: Some(4 * raster.width),
251 rows_per_image: Some(raster.height),
252 },
253 wgpu::Extent3d {
254 width: raster.width,
255 height: raster.height,
256 depth_or_array_layers: 1,
257 },
258 );
259
260 self.atlas_next_x += raster.width + padding;
261 self.atlas_row_height = self.atlas_row_height.max(raster.height);
262
263 info
264 }
265}
266
267fn convert_subpixel_mask_to_rgba(image: &swash::scale::image::Image) -> Vec<u8> {
272 let width = image.placement.width as usize;
273 let height = image.placement.height as usize;
274 let mut pixels = Vec::with_capacity(width * height * 4);
275
276 let stride = if width > 0 && height > 0 {
277 image.data.len() / (width * height)
278 } else {
279 0
280 };
281
282 match stride {
283 3 => {
284 for chunk in image.data.chunks_exact(3) {
285 let r = chunk[0];
286 let g = chunk[1];
287 let b = chunk[2];
288 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
289 pixels.extend_from_slice(&[255, 255, 255, alpha]);
290 }
291 }
292 4 => {
293 for chunk in image.data.chunks_exact(4) {
294 let r = chunk[0];
295 let g = chunk[1];
296 let b = chunk[2];
297 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
299 pixels.extend_from_slice(&[255, 255, 255, alpha]);
300 }
301 }
302 _ => {
303 pixels.resize(width * height * 4, 255);
305 }
306 }
307
308 pixels
309}
310
311fn convert_color_to_alpha_mask(image: &swash::scale::image::Image) -> Vec<u8> {
320 let width = image.placement.width as usize;
321 let height = image.placement.height as usize;
322 let mut pixels = Vec::with_capacity(width * height * 4);
323
324 for chunk in image.data.chunks_exact(4) {
326 let r = chunk[0];
327 let g = chunk[1];
328 let b = chunk[2];
329 let a = chunk[3];
330
331 let luminance = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
334 let alpha = ((luminance as u32 * a as u32) / 255) as u8;
336
337 pixels.extend_from_slice(&[255, 255, 255, alpha]);
338 }
339
340 pixels
341}
342
343#[cfg(test)]
344mod tests {
345 use super::convert_subpixel_mask_to_rgba;
346 use swash::scale::{Render, ScaleContext, Source};
347 use swash::zeno::Format;
348
349 #[test]
350 fn subpixel_mask_uses_rgba_stride() {
351 let data = std::fs::read("../par-term-fonts/fonts/DejaVuSansMono.ttf").expect("font file");
352 let font = swash::FontRef::from_index(&data, 0).expect("font ref");
353 let mut context = ScaleContext::new();
354 let glyph_id = font.charmap().map('a');
355 let mut scaler = context.builder(font).size(18.0).hint(true).build();
356
357 let image = Render::new(&[
358 Source::ColorOutline(0),
359 Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
360 Source::Outline,
361 Source::Bitmap(swash::scale::StrikeWith::BestFit),
362 ])
363 .format(Format::Subpixel)
364 .render(&mut scaler, glyph_id)
365 .expect("render");
366
367 let converted = convert_subpixel_mask_to_rgba(&image);
368
369 let width = image.placement.width as usize;
370 let height = image.placement.height as usize;
371 let mut expected = Vec::with_capacity(width * height * 4);
372 let stride = if width > 0 && height > 0 {
373 image.data.len() / (width * height)
374 } else {
375 0
376 };
377
378 match stride {
379 3 => {
380 for chunk in image.data.chunks_exact(3) {
381 let r = chunk[0];
382 let g = chunk[1];
383 let b = chunk[2];
384 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
385 expected.extend_from_slice(&[255, 255, 255, alpha]);
386 }
387 }
388 4 => {
389 for chunk in image.data.chunks_exact(4) {
390 let r = chunk[0];
391 let g = chunk[1];
392 let b = chunk[2];
393 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
394 expected.extend_from_slice(&[255, 255, 255, alpha]);
395 }
396 }
397 _ => expected.resize(width * height * 4, 255),
398 }
399
400 assert_eq!(converted, expected);
401 }
402
403 use super::should_render_as_symbol;
404
405 #[test]
406 fn test_dingbats_are_symbols() {
407 assert!(
409 should_render_as_symbol('\u{2733}'),
410 "✳ EIGHT SPOKED ASTERISK"
411 );
412 assert!(
413 should_render_as_symbol('\u{2734}'),
414 "✴ EIGHT POINTED BLACK STAR"
415 );
416 assert!(should_render_as_symbol('\u{2747}'), "❇ SPARKLE");
417 assert!(should_render_as_symbol('\u{2744}'), "❄ SNOWFLAKE");
418 assert!(should_render_as_symbol('\u{2702}'), "✂ SCISSORS");
419 assert!(should_render_as_symbol('\u{2714}'), "✔ HEAVY CHECK MARK");
420 assert!(
421 should_render_as_symbol('\u{2716}'),
422 "✖ HEAVY MULTIPLICATION X"
423 );
424 assert!(should_render_as_symbol('\u{2728}'), "✨ SPARKLES");
425 }
426
427 #[test]
428 fn test_misc_symbols_are_symbols() {
429 assert!(should_render_as_symbol('\u{2600}'), "☀ SUN");
431 assert!(should_render_as_symbol('\u{2601}'), "☁ CLOUD");
432 assert!(should_render_as_symbol('\u{263A}'), "☺ SMILING FACE");
433 assert!(should_render_as_symbol('\u{2665}'), "♥ BLACK HEART SUIT");
434 assert!(should_render_as_symbol('\u{2660}'), "♠ BLACK SPADE SUIT");
435 }
436
437 #[test]
438 fn test_misc_symbols_arrows_are_symbols() {
439 assert!(should_render_as_symbol('\u{2B50}'), "⭐ WHITE MEDIUM STAR");
441 assert!(should_render_as_symbol('\u{2B55}'), "⭕ HEAVY LARGE CIRCLE");
442 }
443
444 #[test]
445 fn test_regular_emoji_not_symbols() {
446 assert!(
449 !should_render_as_symbol('\u{1F600}'),
450 "😀 GRINNING FACE should not be a symbol"
451 );
452 assert!(
453 !should_render_as_symbol('\u{1F389}'),
454 "🎉 PARTY POPPER should not be a symbol"
455 );
456 assert!(
457 !should_render_as_symbol('\u{1F44D}'),
458 "👍 THUMBS UP should not be a symbol"
459 );
460 }
461
462 #[test]
463 fn test_regular_chars_not_symbols() {
464 assert!(
466 !should_render_as_symbol('A'),
467 "Letter A should not be a symbol"
468 );
469 assert!(
470 !should_render_as_symbol('*'),
471 "Asterisk should not be a symbol (it's ASCII)"
472 );
473 assert!(
474 !should_render_as_symbol('1'),
475 "Digit 1 should not be a symbol"
476 );
477 }
478}