1use super::{CellRenderer, GlyphInfo};
2use std::collections::HashMap;
3
4pub(crate) struct RasterizedGlyph {
5 pub width: u32,
6 pub height: u32,
7 pub bearing_x: f32,
8 pub bearing_y: f32,
9 pub pixels: Vec<u8>,
10 pub is_colored: bool,
11}
12
13pub(crate) struct GlyphAtlas {
15 pub(crate) atlas_texture: wgpu::Texture,
16 #[allow(dead_code)] pub(crate) atlas_view: wgpu::TextureView,
18 pub(crate) glyph_cache: HashMap<u64, GlyphInfo>,
19 pub(crate) lru_head: Option<u64>,
20 pub(crate) lru_tail: Option<u64>,
21 pub(crate) atlas_next_x: u32,
22 pub(crate) atlas_next_y: u32,
23 pub(crate) atlas_row_height: u32,
24 pub(crate) atlas_size: u32,
26 pub(crate) solid_pixel_offset: (u32, u32),
28}
29
30pub mod symbol_ranges {
35 pub const DINGBATS_START: u32 = 0x2700;
38 pub const DINGBATS_END: u32 = 0x27BF;
39
40 pub const MISC_SYMBOLS_START: u32 = 0x2600;
43 pub const MISC_SYMBOLS_END: u32 = 0x26FF;
44
45 pub const MISC_TECHNICAL_START: u32 = 0x2300;
48 pub const MISC_TECHNICAL_END: u32 = 0x23FF;
49
50 pub const MISC_SYMBOLS_ARROWS_START: u32 = 0x2B00;
53 pub const MISC_SYMBOLS_ARROWS_END: u32 = 0x2BFF;
54}
55
56pub fn should_render_as_symbol(ch: char) -> bool {
65 let code = ch as u32;
66
67 if (symbol_ranges::MISC_TECHNICAL_START..=symbol_ranges::MISC_TECHNICAL_END).contains(&code) {
69 return true;
70 }
71
72 if (symbol_ranges::MISC_SYMBOLS_START..=symbol_ranges::MISC_SYMBOLS_END).contains(&code) {
74 return true;
75 }
76
77 if (symbol_ranges::DINGBATS_START..=symbol_ranges::DINGBATS_END).contains(&code) {
79 return true;
80 }
81
82 if (symbol_ranges::MISC_SYMBOLS_ARROWS_START..=symbol_ranges::MISC_SYMBOLS_ARROWS_END)
84 .contains(&code)
85 {
86 return true;
87 }
88
89 false
90}
91
92impl CellRenderer {
93 pub fn clear_glyph_cache(&mut self) {
94 self.atlas.glyph_cache.clear();
95 self.atlas.lru_head = None;
96 self.atlas.lru_tail = None;
97 self.atlas.atlas_next_x = 0;
98 self.atlas.atlas_next_y = 0;
99 self.atlas.atlas_row_height = 0;
100 self.dirty_rows.fill(true);
101 self.upload_solid_pixel();
103 }
104
105 pub(crate) fn lru_remove(&mut self, key: u64) {
106 let info = self
107 .atlas
108 .glyph_cache
109 .get(&key)
110 .expect("Glyph cache entry must exist before calling lru_remove");
111 let prev = info.prev;
112 let next = info.next;
113
114 if let Some(p) = prev {
115 self.atlas
116 .glyph_cache
117 .get_mut(&p)
118 .expect("Glyph cache LRU prev entry must exist")
119 .next = next;
120 } else {
121 self.atlas.lru_head = next;
122 }
123
124 if let Some(n) = next {
125 self.atlas
126 .glyph_cache
127 .get_mut(&n)
128 .expect("Glyph cache LRU next entry must exist")
129 .prev = prev;
130 } else {
131 self.atlas.lru_tail = prev;
132 }
133 }
134
135 pub(crate) fn lru_push_front(&mut self, key: u64) {
136 let next = self.atlas.lru_head;
137 if let Some(n) = next {
138 self.atlas
139 .glyph_cache
140 .get_mut(&n)
141 .expect("Glyph cache LRU head entry must exist")
142 .prev = Some(key);
143 } else {
144 self.atlas.lru_tail = Some(key);
145 }
146
147 let info = self
148 .atlas
149 .glyph_cache
150 .get_mut(&key)
151 .expect("Glyph cache entry must exist before calling lru_push_front");
152 info.prev = None;
153 info.next = next;
154 self.atlas.lru_head = Some(key);
155 }
156
157 pub(crate) fn rasterize_glyph(
158 &mut self,
159 font_idx: usize,
160 glyph_id: u16,
161 force_monochrome: bool,
162 ) -> Option<RasterizedGlyph> {
163 let font = self.font_manager.get_font(font_idx)?;
164 use swash::scale::Render;
166 use swash::scale::image::Content;
167 use swash::zeno::Format;
168
169 let use_thin_strokes = self.should_use_thin_strokes();
172 let render_format = if !self.font.font_antialias {
173 Format::Alpha
175 } else if use_thin_strokes {
176 Format::Subpixel
178 } else {
179 Format::Alpha
181 };
182
183 let sources = if force_monochrome {
187 [
191 swash::scale::Source::Outline,
192 swash::scale::Source::ColorOutline(0),
193 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
194 ]
195 } else {
196 [
198 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
199 swash::scale::Source::ColorOutline(0),
200 swash::scale::Source::Outline,
201 ]
202 };
203
204 let mut scaler = self
207 .scale_context
208 .builder(*font)
209 .size(self.font.font_size_pixels)
210 .hint(self.font.font_hinting)
211 .build();
212
213 let mut image = Render::new(&sources)
214 .format(render_format)
215 .render(&mut scaler, glyph_id)?;
216
217 if matches!(image.content, Content::Mask) && image.data.iter().all(|&b| b == 0) {
221 if force_monochrome {
222 return None;
227 }
228 #[allow(clippy::drop_non_drop)]
232 drop(scaler);
234 let mut retry_scaler = self
235 .scale_context
236 .builder(*font)
237 .size(self.font.font_size_pixels)
238 .hint(self.font.font_hinting)
239 .build();
240 let color_sources = [
241 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
242 swash::scale::Source::ColorOutline(0),
243 ];
244 image = Render::new(&color_sources)
245 .format(render_format)
246 .render(&mut retry_scaler, glyph_id)?;
247 }
248
249 let (pixels, is_colored) = match image.content {
250 Content::Color => {
251 if force_monochrome {
252 let pixels = convert_color_to_alpha_mask(&image);
257 (pixels, false)
258 } else {
259 (image.data.clone(), true)
260 }
261 }
262 Content::Mask => {
263 let mut pixels = Vec::with_capacity(image.data.len() * 4);
264 for &mask in &image.data {
265 let alpha = if !self.font.font_antialias {
267 if mask > 127 { 255 } else { 0 }
268 } else {
269 mask
270 };
271 pixels.extend_from_slice(&[255, 255, 255, alpha]);
272 }
273 (pixels, false)
274 }
275 Content::SubpixelMask => {
276 let pixels = convert_subpixel_mask_to_rgba(&image);
277 (pixels, false)
278 }
279 };
280
281 if !is_colored && pixels.iter().skip(3).step_by(4).all(|&a| a == 0) {
284 return None;
285 }
286
287 Some(RasterizedGlyph {
288 width: image.placement.width,
289 height: image.placement.height,
290 bearing_x: image.placement.left as f32,
291 bearing_y: image.placement.top as f32,
292 pixels,
293 is_colored,
294 })
295 }
296
297 pub(crate) fn upload_glyph(&mut self, _key: u64, raster: &RasterizedGlyph) -> GlyphInfo {
298 let padding = super::ATLAS_GLYPH_PADDING;
299 let atlas_size = self.atlas.atlas_size;
300 if self.atlas.atlas_next_x + raster.width + padding > atlas_size {
301 self.atlas.atlas_next_x = 0;
302 self.atlas.atlas_next_y += self.atlas.atlas_row_height + padding;
303 self.atlas.atlas_row_height = 0;
304 }
305
306 if self.atlas.atlas_next_y + raster.height + padding > atlas_size {
307 self.clear_glyph_cache();
308 }
309
310 let info = GlyphInfo {
311 key: _key,
312 x: self.atlas.atlas_next_x,
313 y: self.atlas.atlas_next_y,
314 width: raster.width,
315 height: raster.height,
316 bearing_x: raster.bearing_x,
317 bearing_y: raster.bearing_y,
318 is_colored: raster.is_colored,
319 prev: None,
320 next: None,
321 };
322
323 self.queue.write_texture(
324 wgpu::TexelCopyTextureInfo {
325 texture: &self.atlas.atlas_texture,
326 mip_level: 0,
327 origin: wgpu::Origin3d {
328 x: info.x,
329 y: info.y,
330 z: 0,
331 },
332 aspect: wgpu::TextureAspect::All,
333 },
334 &raster.pixels,
335 wgpu::TexelCopyBufferLayout {
336 offset: 0,
337 bytes_per_row: Some(4 * raster.width),
338 rows_per_image: Some(raster.height),
339 },
340 wgpu::Extent3d {
341 width: raster.width,
342 height: raster.height,
343 depth_or_array_layers: 1,
344 },
345 );
346
347 let pad_right_x = info.x + raster.width;
350 let pad_bottom_y = info.y + raster.height;
351
352 if pad_right_x + padding <= atlas_size && raster.height > 0 {
354 let zero = vec![0u8; (padding * raster.height * 4) as usize];
355 self.queue.write_texture(
356 wgpu::TexelCopyTextureInfo {
357 texture: &self.atlas.atlas_texture,
358 mip_level: 0,
359 origin: wgpu::Origin3d {
360 x: pad_right_x,
361 y: info.y,
362 z: 0,
363 },
364 aspect: wgpu::TextureAspect::All,
365 },
366 &zero,
367 wgpu::TexelCopyBufferLayout {
368 offset: 0,
369 bytes_per_row: Some(padding * 4),
370 rows_per_image: Some(raster.height),
371 },
372 wgpu::Extent3d {
373 width: padding,
374 height: raster.height,
375 depth_or_array_layers: 1,
376 },
377 );
378 }
379
380 if pad_bottom_y + padding <= atlas_size && raster.width > 0 {
382 let zero = vec![0u8; (raster.width * padding * 4) as usize];
383 self.queue.write_texture(
384 wgpu::TexelCopyTextureInfo {
385 texture: &self.atlas.atlas_texture,
386 mip_level: 0,
387 origin: wgpu::Origin3d {
388 x: info.x,
389 y: pad_bottom_y,
390 z: 0,
391 },
392 aspect: wgpu::TextureAspect::All,
393 },
394 &zero,
395 wgpu::TexelCopyBufferLayout {
396 offset: 0,
397 bytes_per_row: Some(raster.width * 4),
398 rows_per_image: Some(padding),
399 },
400 wgpu::Extent3d {
401 width: raster.width,
402 height: padding,
403 depth_or_array_layers: 1,
404 },
405 );
406 }
407
408 self.atlas.atlas_next_x += raster.width + padding;
409 self.atlas.atlas_row_height = self.atlas.atlas_row_height.max(raster.height);
410
411 info
412 }
413
414 pub(crate) fn get_or_rasterize_glyph(
423 &mut self,
424 font_idx: usize,
425 glyph_id: u16,
426 force_monochrome: bool,
427 cache_key: u64,
428 ) -> Option<GlyphInfo> {
429 if self.atlas.glyph_cache.contains_key(&cache_key) {
430 self.lru_remove(cache_key);
431 self.lru_push_front(cache_key);
432 return Some(
433 self.atlas
434 .glyph_cache
435 .get(&cache_key)
436 .expect("Glyph cache entry must exist after contains_key check")
437 .clone(),
438 );
439 }
440 let raster = self.rasterize_glyph(font_idx, glyph_id, force_monochrome)?;
441 let info = self.upload_glyph(cache_key, &raster);
442 self.atlas.glyph_cache.insert(cache_key, info.clone());
443 self.lru_push_front(cache_key);
444 Some(info)
445 }
446
447 pub(crate) fn resolve_glyph_with_fallback(
469 &mut self,
470 base_char: char,
471 grapheme: &str,
472 bold: bool,
473 italic: bool,
474 force_monochrome: bool,
475 ) -> Option<GlyphInfo> {
476 let chars: Vec<char> = grapheme.chars().collect();
479 let mut glyph_result = if force_monochrome || chars.len() == 1 {
480 self.font_manager.find_glyph(base_char, bold, italic)
481 } else {
482 self.font_manager
483 .find_grapheme_glyph(grapheme, bold, italic)
484 };
485
486 let mut excluded_fonts: Vec<usize> = Vec::new();
490 let resolved = loop {
491 match glyph_result {
492 Some((font_idx, glyph_id)) => {
493 let cache_key = ((font_idx as u64) << 32) | (glyph_id as u64);
494 if let Some(info) =
495 self.get_or_rasterize_glyph(font_idx, glyph_id, force_monochrome, cache_key)
496 {
497 break Some(info);
498 }
499 excluded_fonts.push(font_idx);
501 glyph_result = self.font_manager.find_glyph_excluding(
502 base_char,
503 bold,
504 italic,
505 &excluded_fonts,
506 );
507 }
508 None => break None,
509 }
510 };
511
512 if resolved.is_none() && force_monochrome {
518 let mut glyph_result2 = self.font_manager.find_glyph(base_char, bold, italic);
519 loop {
520 match glyph_result2 {
521 Some((font_idx, glyph_id)) => {
522 let cache_key =
523 ((font_idx as u64) << 32) | (glyph_id as u64) | (1u64 << 63);
524 if let Some(info) =
525 self.get_or_rasterize_glyph(font_idx, glyph_id, false, cache_key)
526 {
527 break Some(info);
528 }
529 glyph_result2 = self.font_manager.find_glyph_excluding(
530 base_char,
531 bold,
532 italic,
533 &[font_idx],
534 );
535 }
536 None => break None,
537 }
538 }
539 } else {
540 resolved
541 }
542 }
543}
544
545fn convert_subpixel_mask_to_rgba(image: &swash::scale::image::Image) -> Vec<u8> {
550 let width = image.placement.width as usize;
551 let height = image.placement.height as usize;
552 let mut pixels = Vec::with_capacity(width * height * 4);
553
554 let stride = if width > 0 && height > 0 {
555 image.data.len() / (width * height)
556 } else {
557 0
558 };
559
560 match stride {
561 3 => {
562 for chunk in image.data.as_chunks::<3>().0 {
563 let r = chunk[0];
564 let g = chunk[1];
565 let b = chunk[2];
566 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
567 pixels.extend_from_slice(&[255, 255, 255, alpha]);
568 }
569 }
570 4 => {
571 for chunk in image.data.as_chunks::<4>().0 {
572 let r = chunk[0];
573 let g = chunk[1];
574 let b = chunk[2];
575 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
577 pixels.extend_from_slice(&[255, 255, 255, alpha]);
578 }
579 }
580 _ => {
581 pixels.resize(width * height * 4, 255);
583 }
584 }
585
586 pixels
587}
588
589fn convert_color_to_alpha_mask(image: &swash::scale::image::Image) -> Vec<u8> {
600 let width = image.placement.width as usize;
601 let height = image.placement.height as usize;
602 let mut pixels = Vec::with_capacity(width * height * 4);
603
604 for chunk in image.data.as_chunks::<4>().0 {
607 let a = chunk[3];
608 pixels.extend_from_slice(&[255, 255, 255, a]);
609 }
610
611 pixels
612}
613
614#[cfg(test)]
615mod tests {
616 use super::convert_subpixel_mask_to_rgba;
617 use swash::scale::{Render, ScaleContext, Source};
618 use swash::zeno::Format;
619
620 #[test]
621 fn subpixel_mask_uses_rgba_stride() {
622 let data = std::fs::read("../par-term-fonts/fonts/DejaVuSansMono.ttf").expect("font file");
623 let font = swash::FontRef::from_index(&data, 0).expect("font ref");
624 let mut context = ScaleContext::new();
625 let glyph_id = font.charmap().map('a');
626 let mut scaler = context.builder(font).size(18.0).hint(true).build();
627
628 let image = Render::new(&[
629 Source::ColorOutline(0),
630 Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
631 Source::Outline,
632 Source::Bitmap(swash::scale::StrikeWith::BestFit),
633 ])
634 .format(Format::Subpixel)
635 .render(&mut scaler, glyph_id)
636 .expect("render");
637
638 let converted = convert_subpixel_mask_to_rgba(&image);
639
640 let width = image.placement.width as usize;
641 let height = image.placement.height as usize;
642 let mut expected = Vec::with_capacity(width * height * 4);
643 let stride = if width > 0 && height > 0 {
644 image.data.len() / (width * height)
645 } else {
646 0
647 };
648
649 match stride {
650 3 => {
651 for chunk in image.data.as_chunks::<3>().0 {
652 let r = chunk[0];
653 let g = chunk[1];
654 let b = chunk[2];
655 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
656 expected.extend_from_slice(&[255, 255, 255, alpha]);
657 }
658 }
659 4 => {
660 for chunk in image.data.as_chunks::<4>().0 {
661 let r = chunk[0];
662 let g = chunk[1];
663 let b = chunk[2];
664 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
665 expected.extend_from_slice(&[255, 255, 255, alpha]);
666 }
667 }
668 _ => expected.resize(width * height * 4, 255),
669 }
670
671 assert_eq!(converted, expected);
672 }
673
674 use super::should_render_as_symbol;
675
676 #[test]
677 fn test_dingbats_are_symbols() {
678 assert!(
680 should_render_as_symbol('\u{2733}'),
681 "✳ EIGHT SPOKED ASTERISK"
682 );
683 assert!(
684 should_render_as_symbol('\u{2734}'),
685 "✴ EIGHT POINTED BLACK STAR"
686 );
687 assert!(should_render_as_symbol('\u{2747}'), "❇ SPARKLE");
688 assert!(should_render_as_symbol('\u{2744}'), "❄ SNOWFLAKE");
689 assert!(should_render_as_symbol('\u{2702}'), "✂ SCISSORS");
690 assert!(should_render_as_symbol('\u{2714}'), "✔ HEAVY CHECK MARK");
691 assert!(
692 should_render_as_symbol('\u{2716}'),
693 "✖ HEAVY MULTIPLICATION X"
694 );
695 assert!(should_render_as_symbol('\u{2728}'), "✨ SPARKLES");
696 }
697
698 #[test]
699 fn test_misc_symbols_are_symbols() {
700 assert!(should_render_as_symbol('\u{2600}'), "☀ SUN");
702 assert!(should_render_as_symbol('\u{2601}'), "☁ CLOUD");
703 assert!(should_render_as_symbol('\u{263A}'), "☺ SMILING FACE");
704 assert!(should_render_as_symbol('\u{2665}'), "♥ BLACK HEART SUIT");
705 assert!(should_render_as_symbol('\u{2660}'), "♠ BLACK SPADE SUIT");
706 }
707
708 #[test]
709 fn test_misc_symbols_arrows_are_symbols() {
710 assert!(should_render_as_symbol('\u{2B50}'), "⭐ WHITE MEDIUM STAR");
712 assert!(should_render_as_symbol('\u{2B55}'), "⭕ HEAVY LARGE CIRCLE");
713 }
714
715 #[test]
716 fn test_regular_emoji_not_symbols() {
717 assert!(
720 !should_render_as_symbol('\u{1F600}'),
721 "😀 GRINNING FACE should not be a symbol"
722 );
723 assert!(
724 !should_render_as_symbol('\u{1F389}'),
725 "🎉 PARTY POPPER should not be a symbol"
726 );
727 assert!(
728 !should_render_as_symbol('\u{1F44D}'),
729 "👍 THUMBS UP should not be a symbol"
730 );
731 }
732
733 #[test]
734 fn test_regular_chars_not_symbols() {
735 assert!(
737 !should_render_as_symbol('A'),
738 "Letter A should not be a symbol"
739 );
740 assert!(
741 !should_render_as_symbol('*'),
742 "Asterisk should not be a symbol (it's ASCII)"
743 );
744 assert!(
745 !should_render_as_symbol('1'),
746 "Digit 1 should not be a symbol"
747 );
748 }
749}