par_term_render/cell_renderer/
atlas.rs1use super::{CellRenderer, GlyphInfo};
2
3pub(crate) struct RasterizedGlyph {
4 pub width: u32,
5 pub height: u32,
6 pub bearing_x: f32,
7 pub bearing_y: f32,
8 pub pixels: Vec<u8>,
9 pub is_colored: bool,
10}
11
12pub mod symbol_ranges {
17 pub const DINGBATS_START: u32 = 0x2700;
20 pub const DINGBATS_END: u32 = 0x27BF;
21
22 pub const MISC_SYMBOLS_START: u32 = 0x2600;
25 pub const MISC_SYMBOLS_END: u32 = 0x26FF;
26
27 pub const MISC_TECHNICAL_START: u32 = 0x2300;
30 pub const MISC_TECHNICAL_END: u32 = 0x23FF;
31
32 pub const MISC_SYMBOLS_ARROWS_START: u32 = 0x2B00;
35 pub const MISC_SYMBOLS_ARROWS_END: u32 = 0x2BFF;
36}
37
38pub fn should_render_as_symbol(ch: char) -> bool {
47 let code = ch as u32;
48
49 if (symbol_ranges::MISC_TECHNICAL_START..=symbol_ranges::MISC_TECHNICAL_END).contains(&code) {
51 return true;
52 }
53
54 if (symbol_ranges::MISC_SYMBOLS_START..=symbol_ranges::MISC_SYMBOLS_END).contains(&code) {
56 return true;
57 }
58
59 if (symbol_ranges::DINGBATS_START..=symbol_ranges::DINGBATS_END).contains(&code) {
61 return true;
62 }
63
64 if (symbol_ranges::MISC_SYMBOLS_ARROWS_START..=symbol_ranges::MISC_SYMBOLS_ARROWS_END)
66 .contains(&code)
67 {
68 return true;
69 }
70
71 false
72}
73
74impl CellRenderer {
75 pub fn clear_glyph_cache(&mut self) {
76 self.atlas.glyph_cache.clear();
77 self.atlas.lru_head = None;
78 self.atlas.lru_tail = None;
79 self.atlas.atlas_next_x = 0;
80 self.atlas.atlas_next_y = 0;
81 self.atlas.atlas_row_height = 0;
82 self.dirty_rows.fill(true);
83 self.upload_solid_pixel();
85 }
86
87 pub(crate) fn lru_remove(&mut self, key: u64) {
88 let info = self
89 .atlas
90 .glyph_cache
91 .get(&key)
92 .expect("Glyph cache entry must exist before calling lru_remove");
93 let prev = info.prev;
94 let next = info.next;
95
96 if let Some(p) = prev {
97 self.atlas
98 .glyph_cache
99 .get_mut(&p)
100 .expect("Glyph cache LRU prev entry must exist")
101 .next = next;
102 } else {
103 self.atlas.lru_head = next;
104 }
105
106 if let Some(n) = next {
107 self.atlas
108 .glyph_cache
109 .get_mut(&n)
110 .expect("Glyph cache LRU next entry must exist")
111 .prev = prev;
112 } else {
113 self.atlas.lru_tail = prev;
114 }
115 }
116
117 pub(crate) fn lru_push_front(&mut self, key: u64) {
118 let next = self.atlas.lru_head;
119 if let Some(n) = next {
120 self.atlas
121 .glyph_cache
122 .get_mut(&n)
123 .expect("Glyph cache LRU head entry must exist")
124 .prev = Some(key);
125 } else {
126 self.atlas.lru_tail = Some(key);
127 }
128
129 let info = self
130 .atlas
131 .glyph_cache
132 .get_mut(&key)
133 .expect("Glyph cache entry must exist before calling lru_push_front");
134 info.prev = None;
135 info.next = next;
136 self.atlas.lru_head = Some(key);
137 }
138
139 pub(crate) fn rasterize_glyph(
140 &mut self,
141 font_idx: usize,
142 glyph_id: u16,
143 force_monochrome: bool,
144 ) -> Option<RasterizedGlyph> {
145 let font = self.font_manager.get_font(font_idx)?;
146 use swash::scale::Render;
148 use swash::scale::image::Content;
149 use swash::zeno::Format;
150
151 let use_thin_strokes = self.should_use_thin_strokes();
154 let render_format = if !self.font.font_antialias {
155 Format::Alpha
157 } else if use_thin_strokes {
158 Format::Subpixel
160 } else {
161 Format::Alpha
163 };
164
165 let sources = if force_monochrome {
169 [
173 swash::scale::Source::Outline,
174 swash::scale::Source::ColorOutline(0),
175 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
176 ]
177 } else {
178 [
180 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
181 swash::scale::Source::ColorOutline(0),
182 swash::scale::Source::Outline,
183 ]
184 };
185
186 let mut scaler = self
189 .scale_context
190 .builder(*font)
191 .size(self.font.font_size_pixels)
192 .hint(self.font.font_hinting)
193 .build();
194
195 let mut image = Render::new(&sources)
196 .format(render_format)
197 .render(&mut scaler, glyph_id)?;
198
199 if matches!(image.content, Content::Mask) && image.data.iter().all(|&b| b == 0) {
203 if force_monochrome {
204 return None;
209 }
210 #[allow(clippy::drop_non_drop)]
214 drop(scaler);
216 let mut retry_scaler = self
217 .scale_context
218 .builder(*font)
219 .size(self.font.font_size_pixels)
220 .hint(self.font.font_hinting)
221 .build();
222 let color_sources = [
223 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
224 swash::scale::Source::ColorOutline(0),
225 ];
226 if let Some(color_image) = Render::new(&color_sources)
227 .format(render_format)
228 .render(&mut retry_scaler, glyph_id)
229 {
230 image = color_image;
231 } else {
232 return None;
233 }
234 }
235
236 let (pixels, is_colored) = match image.content {
237 Content::Color => {
238 if force_monochrome {
239 let pixels = convert_color_to_alpha_mask(&image);
244 (pixels, false)
245 } else {
246 (image.data.clone(), true)
247 }
248 }
249 Content::Mask => {
250 let mut pixels = Vec::with_capacity(image.data.len() * 4);
251 for &mask in &image.data {
252 let alpha = if !self.font.font_antialias {
254 if mask > 127 { 255 } else { 0 }
255 } else {
256 mask
257 };
258 pixels.extend_from_slice(&[255, 255, 255, alpha]);
259 }
260 (pixels, false)
261 }
262 Content::SubpixelMask => {
263 let pixels = convert_subpixel_mask_to_rgba(&image);
264 (pixels, false)
265 }
266 };
267
268 if !is_colored && pixels.iter().skip(3).step_by(4).all(|&a| a == 0) {
271 return None;
272 }
273
274 Some(RasterizedGlyph {
275 width: image.placement.width,
276 height: image.placement.height,
277 bearing_x: image.placement.left as f32,
278 bearing_y: image.placement.top as f32,
279 pixels,
280 is_colored,
281 })
282 }
283
284 pub(crate) fn upload_glyph(&mut self, _key: u64, raster: &RasterizedGlyph) -> GlyphInfo {
285 let padding = super::ATLAS_GLYPH_PADDING;
286 let atlas_size = self.atlas.atlas_size;
287 if self.atlas.atlas_next_x + raster.width + padding > atlas_size {
288 self.atlas.atlas_next_x = 0;
289 self.atlas.atlas_next_y += self.atlas.atlas_row_height + padding;
290 self.atlas.atlas_row_height = 0;
291 }
292
293 if self.atlas.atlas_next_y + raster.height + padding > atlas_size {
294 self.clear_glyph_cache();
295 }
296
297 let info = GlyphInfo {
298 key: _key,
299 x: self.atlas.atlas_next_x,
300 y: self.atlas.atlas_next_y,
301 width: raster.width,
302 height: raster.height,
303 bearing_x: raster.bearing_x,
304 bearing_y: raster.bearing_y,
305 is_colored: raster.is_colored,
306 prev: None,
307 next: None,
308 };
309
310 self.queue.write_texture(
311 wgpu::TexelCopyTextureInfo {
312 texture: &self.atlas.atlas_texture,
313 mip_level: 0,
314 origin: wgpu::Origin3d {
315 x: info.x,
316 y: info.y,
317 z: 0,
318 },
319 aspect: wgpu::TextureAspect::All,
320 },
321 &raster.pixels,
322 wgpu::TexelCopyBufferLayout {
323 offset: 0,
324 bytes_per_row: Some(4 * raster.width),
325 rows_per_image: Some(raster.height),
326 },
327 wgpu::Extent3d {
328 width: raster.width,
329 height: raster.height,
330 depth_or_array_layers: 1,
331 },
332 );
333
334 let pad_right_x = info.x + raster.width;
337 let pad_bottom_y = info.y + raster.height;
338
339 if pad_right_x + padding <= atlas_size && raster.height > 0 {
341 let zero = vec![0u8; (padding * raster.height * 4) as usize];
342 self.queue.write_texture(
343 wgpu::TexelCopyTextureInfo {
344 texture: &self.atlas.atlas_texture,
345 mip_level: 0,
346 origin: wgpu::Origin3d {
347 x: pad_right_x,
348 y: info.y,
349 z: 0,
350 },
351 aspect: wgpu::TextureAspect::All,
352 },
353 &zero,
354 wgpu::TexelCopyBufferLayout {
355 offset: 0,
356 bytes_per_row: Some(padding * 4),
357 rows_per_image: Some(raster.height),
358 },
359 wgpu::Extent3d {
360 width: padding,
361 height: raster.height,
362 depth_or_array_layers: 1,
363 },
364 );
365 }
366
367 if pad_bottom_y + padding <= atlas_size && raster.width > 0 {
369 let zero = vec![0u8; (raster.width * padding * 4) as usize];
370 self.queue.write_texture(
371 wgpu::TexelCopyTextureInfo {
372 texture: &self.atlas.atlas_texture,
373 mip_level: 0,
374 origin: wgpu::Origin3d {
375 x: info.x,
376 y: pad_bottom_y,
377 z: 0,
378 },
379 aspect: wgpu::TextureAspect::All,
380 },
381 &zero,
382 wgpu::TexelCopyBufferLayout {
383 offset: 0,
384 bytes_per_row: Some(raster.width * 4),
385 rows_per_image: Some(padding),
386 },
387 wgpu::Extent3d {
388 width: raster.width,
389 height: padding,
390 depth_or_array_layers: 1,
391 },
392 );
393 }
394
395 self.atlas.atlas_next_x += raster.width + padding;
396 self.atlas.atlas_row_height = self.atlas.atlas_row_height.max(raster.height);
397
398 info
399 }
400
401 pub(crate) fn get_or_rasterize_glyph(
410 &mut self,
411 font_idx: usize,
412 glyph_id: u16,
413 force_monochrome: bool,
414 cache_key: u64,
415 ) -> Option<GlyphInfo> {
416 if self.atlas.glyph_cache.contains_key(&cache_key) {
417 self.lru_remove(cache_key);
418 self.lru_push_front(cache_key);
419 return Some(
420 self.atlas
421 .glyph_cache
422 .get(&cache_key)
423 .expect("Glyph cache entry must exist after contains_key check")
424 .clone(),
425 );
426 }
427 let raster = self.rasterize_glyph(font_idx, glyph_id, force_monochrome)?;
428 let info = self.upload_glyph(cache_key, &raster);
429 self.atlas.glyph_cache.insert(cache_key, info.clone());
430 self.lru_push_front(cache_key);
431 Some(info)
432 }
433}
434
435fn convert_subpixel_mask_to_rgba(image: &swash::scale::image::Image) -> Vec<u8> {
440 let width = image.placement.width as usize;
441 let height = image.placement.height as usize;
442 let mut pixels = Vec::with_capacity(width * height * 4);
443
444 let stride = if width > 0 && height > 0 {
445 image.data.len() / (width * height)
446 } else {
447 0
448 };
449
450 match stride {
451 3 => {
452 for chunk in image.data.chunks_exact(3) {
453 let r = chunk[0];
454 let g = chunk[1];
455 let b = chunk[2];
456 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
457 pixels.extend_from_slice(&[255, 255, 255, alpha]);
458 }
459 }
460 4 => {
461 for chunk in image.data.chunks_exact(4) {
462 let r = chunk[0];
463 let g = chunk[1];
464 let b = chunk[2];
465 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
467 pixels.extend_from_slice(&[255, 255, 255, alpha]);
468 }
469 }
470 _ => {
471 pixels.resize(width * height * 4, 255);
473 }
474 }
475
476 pixels
477}
478
479fn convert_color_to_alpha_mask(image: &swash::scale::image::Image) -> Vec<u8> {
490 let width = image.placement.width as usize;
491 let height = image.placement.height as usize;
492 let mut pixels = Vec::with_capacity(width * height * 4);
493
494 for chunk in image.data.chunks_exact(4) {
497 let a = chunk[3];
498 pixels.extend_from_slice(&[255, 255, 255, a]);
499 }
500
501 pixels
502}
503
504#[cfg(test)]
505mod tests {
506 use super::convert_subpixel_mask_to_rgba;
507 use swash::scale::{Render, ScaleContext, Source};
508 use swash::zeno::Format;
509
510 #[test]
511 fn subpixel_mask_uses_rgba_stride() {
512 let data = std::fs::read("../par-term-fonts/fonts/DejaVuSansMono.ttf").expect("font file");
513 let font = swash::FontRef::from_index(&data, 0).expect("font ref");
514 let mut context = ScaleContext::new();
515 let glyph_id = font.charmap().map('a');
516 let mut scaler = context.builder(font).size(18.0).hint(true).build();
517
518 let image = Render::new(&[
519 Source::ColorOutline(0),
520 Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
521 Source::Outline,
522 Source::Bitmap(swash::scale::StrikeWith::BestFit),
523 ])
524 .format(Format::Subpixel)
525 .render(&mut scaler, glyph_id)
526 .expect("render");
527
528 let converted = convert_subpixel_mask_to_rgba(&image);
529
530 let width = image.placement.width as usize;
531 let height = image.placement.height as usize;
532 let mut expected = Vec::with_capacity(width * height * 4);
533 let stride = if width > 0 && height > 0 {
534 image.data.len() / (width * height)
535 } else {
536 0
537 };
538
539 match stride {
540 3 => {
541 for chunk in image.data.chunks_exact(3) {
542 let r = chunk[0];
543 let g = chunk[1];
544 let b = chunk[2];
545 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
546 expected.extend_from_slice(&[255, 255, 255, alpha]);
547 }
548 }
549 4 => {
550 for chunk in image.data.chunks_exact(4) {
551 let r = chunk[0];
552 let g = chunk[1];
553 let b = chunk[2];
554 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
555 expected.extend_from_slice(&[255, 255, 255, alpha]);
556 }
557 }
558 _ => expected.resize(width * height * 4, 255),
559 }
560
561 assert_eq!(converted, expected);
562 }
563
564 use super::should_render_as_symbol;
565
566 #[test]
567 fn test_dingbats_are_symbols() {
568 assert!(
570 should_render_as_symbol('\u{2733}'),
571 "✳ EIGHT SPOKED ASTERISK"
572 );
573 assert!(
574 should_render_as_symbol('\u{2734}'),
575 "✴ EIGHT POINTED BLACK STAR"
576 );
577 assert!(should_render_as_symbol('\u{2747}'), "❇ SPARKLE");
578 assert!(should_render_as_symbol('\u{2744}'), "❄ SNOWFLAKE");
579 assert!(should_render_as_symbol('\u{2702}'), "✂ SCISSORS");
580 assert!(should_render_as_symbol('\u{2714}'), "✔ HEAVY CHECK MARK");
581 assert!(
582 should_render_as_symbol('\u{2716}'),
583 "✖ HEAVY MULTIPLICATION X"
584 );
585 assert!(should_render_as_symbol('\u{2728}'), "✨ SPARKLES");
586 }
587
588 #[test]
589 fn test_misc_symbols_are_symbols() {
590 assert!(should_render_as_symbol('\u{2600}'), "☀ SUN");
592 assert!(should_render_as_symbol('\u{2601}'), "☁ CLOUD");
593 assert!(should_render_as_symbol('\u{263A}'), "☺ SMILING FACE");
594 assert!(should_render_as_symbol('\u{2665}'), "♥ BLACK HEART SUIT");
595 assert!(should_render_as_symbol('\u{2660}'), "♠ BLACK SPADE SUIT");
596 }
597
598 #[test]
599 fn test_misc_symbols_arrows_are_symbols() {
600 assert!(should_render_as_symbol('\u{2B50}'), "⭐ WHITE MEDIUM STAR");
602 assert!(should_render_as_symbol('\u{2B55}'), "⭕ HEAVY LARGE CIRCLE");
603 }
604
605 #[test]
606 fn test_regular_emoji_not_symbols() {
607 assert!(
610 !should_render_as_symbol('\u{1F600}'),
611 "😀 GRINNING FACE should not be a symbol"
612 );
613 assert!(
614 !should_render_as_symbol('\u{1F389}'),
615 "🎉 PARTY POPPER should not be a symbol"
616 );
617 assert!(
618 !should_render_as_symbol('\u{1F44D}'),
619 "👍 THUMBS UP should not be a symbol"
620 );
621 }
622
623 #[test]
624 fn test_regular_chars_not_symbols() {
625 assert!(
627 !should_render_as_symbol('A'),
628 "Letter A should not be a symbol"
629 );
630 assert!(
631 !should_render_as_symbol('*'),
632 "Asterisk should not be a symbol (it's ASCII)"
633 );
634 assert!(
635 !should_render_as_symbol('1'),
636 "Digit 1 should not be a symbol"
637 );
638 }
639}