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_TECHNICAL_START: u32 = 0x2300;
32 pub const MISC_TECHNICAL_END: u32 = 0x23FF;
33
34 pub const MISC_SYMBOLS_ARROWS_START: u32 = 0x2B00;
37 pub const MISC_SYMBOLS_ARROWS_END: u32 = 0x2BFF;
38}
39
40pub fn should_render_as_symbol(ch: char) -> bool {
49 let code = ch as u32;
50
51 if (symbol_ranges::MISC_TECHNICAL_START..=symbol_ranges::MISC_TECHNICAL_END).contains(&code) {
53 return true;
54 }
55
56 if (symbol_ranges::MISC_SYMBOLS_START..=symbol_ranges::MISC_SYMBOLS_END).contains(&code) {
58 return true;
59 }
60
61 if (symbol_ranges::DINGBATS_START..=symbol_ranges::DINGBATS_END).contains(&code) {
63 return true;
64 }
65
66 if (symbol_ranges::MISC_SYMBOLS_ARROWS_START..=symbol_ranges::MISC_SYMBOLS_ARROWS_END)
68 .contains(&code)
69 {
70 return true;
71 }
72
73 false
74}
75
76impl CellRenderer {
77 pub fn clear_glyph_cache(&mut self) {
78 self.glyph_cache.clear();
79 self.lru_head = None;
80 self.lru_tail = None;
81 self.atlas_next_x = 0;
82 self.atlas_next_y = 0;
83 self.atlas_row_height = 0;
84 self.dirty_rows.fill(true);
85 self.upload_solid_pixel();
87 }
88
89 pub(crate) fn lru_remove(&mut self, key: u64) {
90 let info = self.glyph_cache.get(&key).unwrap();
91 let prev = info.prev;
92 let next = info.next;
93
94 if let Some(p) = prev {
95 self.glyph_cache.get_mut(&p).unwrap().next = next;
96 } else {
97 self.lru_head = next;
98 }
99
100 if let Some(n) = next {
101 self.glyph_cache.get_mut(&n).unwrap().prev = prev;
102 } else {
103 self.lru_tail = prev;
104 }
105 }
106
107 pub(crate) fn lru_push_front(&mut self, key: u64) {
108 let next = self.lru_head;
109 if let Some(n) = next {
110 self.glyph_cache.get_mut(&n).unwrap().prev = Some(key);
111 } else {
112 self.lru_tail = Some(key);
113 }
114
115 let info = self.glyph_cache.get_mut(&key).unwrap();
116 info.prev = None;
117 info.next = next;
118 self.lru_head = Some(key);
119 }
120
121 pub(crate) fn rasterize_glyph(
122 &self,
123 font_idx: usize,
124 glyph_id: u16,
125 force_monochrome: bool,
126 ) -> Option<RasterizedGlyph> {
127 let font = self.font_manager.get_font(font_idx)?;
128 use swash::scale::image::Content;
130 use swash::scale::{Render, ScaleContext};
131 use swash::zeno::Format;
132
133 let mut context = ScaleContext::new();
134
135 let mut scaler = context
137 .builder(*font)
138 .size(self.font_size_pixels)
139 .hint(self.font_hinting)
140 .build();
141
142 let use_thin_strokes = self.should_use_thin_strokes();
144 let render_format = if !self.font_antialias {
145 Format::Alpha
147 } else if use_thin_strokes {
148 Format::Subpixel
150 } else {
151 Format::Alpha
153 };
154
155 let sources = if force_monochrome {
159 [
163 swash::scale::Source::Outline,
164 swash::scale::Source::ColorOutline(0),
165 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
166 ]
167 } else {
168 [
170 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
171 swash::scale::Source::ColorOutline(0),
172 swash::scale::Source::Outline,
173 ]
174 };
175
176 let mut image = Render::new(&sources)
177 .format(render_format)
178 .render(&mut scaler, glyph_id)?;
179
180 if matches!(image.content, Content::Mask) && image.data.iter().all(|&b| b == 0) {
184 if force_monochrome {
185 return None;
190 }
191 let mut retry_ctx = ScaleContext::new();
193 let mut retry_scaler = retry_ctx
194 .builder(*font)
195 .size(self.font_size_pixels)
196 .hint(self.font_hinting)
197 .build();
198 let color_sources = [
199 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
200 swash::scale::Source::ColorOutline(0),
201 ];
202 if let Some(color_image) = Render::new(&color_sources)
203 .format(render_format)
204 .render(&mut retry_scaler, glyph_id)
205 {
206 image = color_image;
207 } else {
208 return None;
209 }
210 }
211
212 let (pixels, is_colored) = match image.content {
213 Content::Color => {
214 if force_monochrome {
215 let pixels = convert_color_to_alpha_mask(&image);
220 (pixels, false)
221 } else {
222 (image.data.clone(), true)
223 }
224 }
225 Content::Mask => {
226 let mut pixels = Vec::with_capacity(image.data.len() * 4);
227 for &mask in &image.data {
228 let alpha = if !self.font_antialias {
230 if mask > 127 { 255 } else { 0 }
231 } else {
232 mask
233 };
234 pixels.extend_from_slice(&[255, 255, 255, alpha]);
235 }
236 (pixels, false)
237 }
238 Content::SubpixelMask => {
239 let pixels = convert_subpixel_mask_to_rgba(&image);
240 (pixels, false)
241 }
242 };
243
244 if !is_colored && pixels.iter().skip(3).step_by(4).all(|&a| a == 0) {
247 return None;
248 }
249
250 Some(RasterizedGlyph {
251 width: image.placement.width,
252 height: image.placement.height,
253 bearing_x: image.placement.left as f32,
254 bearing_y: image.placement.top as f32,
255 pixels,
256 is_colored,
257 })
258 }
259
260 pub(crate) fn upload_glyph(&mut self, _key: u64, raster: &RasterizedGlyph) -> GlyphInfo {
261 let padding = 2;
262 if self.atlas_next_x + raster.width + padding > 2048 {
263 self.atlas_next_x = 0;
264 self.atlas_next_y += self.atlas_row_height + padding;
265 self.atlas_row_height = 0;
266 }
267
268 if self.atlas_next_y + raster.height + padding > 2048 {
269 self.clear_glyph_cache();
270 }
271
272 let info = GlyphInfo {
273 key: _key,
274 x: self.atlas_next_x,
275 y: self.atlas_next_y,
276 width: raster.width,
277 height: raster.height,
278 bearing_x: raster.bearing_x,
279 bearing_y: raster.bearing_y,
280 is_colored: raster.is_colored,
281 prev: None,
282 next: None,
283 };
284
285 self.queue.write_texture(
286 wgpu::TexelCopyTextureInfo {
287 texture: &self.atlas_texture,
288 mip_level: 0,
289 origin: wgpu::Origin3d {
290 x: info.x,
291 y: info.y,
292 z: 0,
293 },
294 aspect: wgpu::TextureAspect::All,
295 },
296 &raster.pixels,
297 wgpu::TexelCopyBufferLayout {
298 offset: 0,
299 bytes_per_row: Some(4 * raster.width),
300 rows_per_image: Some(raster.height),
301 },
302 wgpu::Extent3d {
303 width: raster.width,
304 height: raster.height,
305 depth_or_array_layers: 1,
306 },
307 );
308
309 let pad_right_x = info.x + raster.width;
312 let pad_bottom_y = info.y + raster.height;
313
314 if pad_right_x + padding <= 2048 && raster.height > 0 {
316 let zero = vec![0u8; (padding * raster.height * 4) as usize];
317 self.queue.write_texture(
318 wgpu::TexelCopyTextureInfo {
319 texture: &self.atlas_texture,
320 mip_level: 0,
321 origin: wgpu::Origin3d {
322 x: pad_right_x,
323 y: info.y,
324 z: 0,
325 },
326 aspect: wgpu::TextureAspect::All,
327 },
328 &zero,
329 wgpu::TexelCopyBufferLayout {
330 offset: 0,
331 bytes_per_row: Some(padding * 4),
332 rows_per_image: Some(raster.height),
333 },
334 wgpu::Extent3d {
335 width: padding,
336 height: raster.height,
337 depth_or_array_layers: 1,
338 },
339 );
340 }
341
342 if pad_bottom_y + padding <= 2048 && raster.width > 0 {
344 let zero = vec![0u8; (raster.width * padding * 4) as usize];
345 self.queue.write_texture(
346 wgpu::TexelCopyTextureInfo {
347 texture: &self.atlas_texture,
348 mip_level: 0,
349 origin: wgpu::Origin3d {
350 x: info.x,
351 y: pad_bottom_y,
352 z: 0,
353 },
354 aspect: wgpu::TextureAspect::All,
355 },
356 &zero,
357 wgpu::TexelCopyBufferLayout {
358 offset: 0,
359 bytes_per_row: Some(raster.width * 4),
360 rows_per_image: Some(padding),
361 },
362 wgpu::Extent3d {
363 width: raster.width,
364 height: padding,
365 depth_or_array_layers: 1,
366 },
367 );
368 }
369
370 self.atlas_next_x += raster.width + padding;
371 self.atlas_row_height = self.atlas_row_height.max(raster.height);
372
373 info
374 }
375}
376
377fn convert_subpixel_mask_to_rgba(image: &swash::scale::image::Image) -> Vec<u8> {
382 let width = image.placement.width as usize;
383 let height = image.placement.height as usize;
384 let mut pixels = Vec::with_capacity(width * height * 4);
385
386 let stride = if width > 0 && height > 0 {
387 image.data.len() / (width * height)
388 } else {
389 0
390 };
391
392 match stride {
393 3 => {
394 for chunk in image.data.chunks_exact(3) {
395 let r = chunk[0];
396 let g = chunk[1];
397 let b = chunk[2];
398 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
399 pixels.extend_from_slice(&[255, 255, 255, alpha]);
400 }
401 }
402 4 => {
403 for chunk in image.data.chunks_exact(4) {
404 let r = chunk[0];
405 let g = chunk[1];
406 let b = chunk[2];
407 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
409 pixels.extend_from_slice(&[255, 255, 255, alpha]);
410 }
411 }
412 _ => {
413 pixels.resize(width * height * 4, 255);
415 }
416 }
417
418 pixels
419}
420
421fn convert_color_to_alpha_mask(image: &swash::scale::image::Image) -> Vec<u8> {
432 let width = image.placement.width as usize;
433 let height = image.placement.height as usize;
434 let mut pixels = Vec::with_capacity(width * height * 4);
435
436 for chunk in image.data.chunks_exact(4) {
439 let a = chunk[3];
440 pixels.extend_from_slice(&[255, 255, 255, a]);
441 }
442
443 pixels
444}
445
446#[cfg(test)]
447mod tests {
448 use super::convert_subpixel_mask_to_rgba;
449 use swash::scale::{Render, ScaleContext, Source};
450 use swash::zeno::Format;
451
452 #[test]
453 fn subpixel_mask_uses_rgba_stride() {
454 let data = std::fs::read("../par-term-fonts/fonts/DejaVuSansMono.ttf").expect("font file");
455 let font = swash::FontRef::from_index(&data, 0).expect("font ref");
456 let mut context = ScaleContext::new();
457 let glyph_id = font.charmap().map('a');
458 let mut scaler = context.builder(font).size(18.0).hint(true).build();
459
460 let image = Render::new(&[
461 Source::ColorOutline(0),
462 Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
463 Source::Outline,
464 Source::Bitmap(swash::scale::StrikeWith::BestFit),
465 ])
466 .format(Format::Subpixel)
467 .render(&mut scaler, glyph_id)
468 .expect("render");
469
470 let converted = convert_subpixel_mask_to_rgba(&image);
471
472 let width = image.placement.width as usize;
473 let height = image.placement.height as usize;
474 let mut expected = Vec::with_capacity(width * height * 4);
475 let stride = if width > 0 && height > 0 {
476 image.data.len() / (width * height)
477 } else {
478 0
479 };
480
481 match stride {
482 3 => {
483 for chunk in image.data.chunks_exact(3) {
484 let r = chunk[0];
485 let g = chunk[1];
486 let b = chunk[2];
487 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
488 expected.extend_from_slice(&[255, 255, 255, alpha]);
489 }
490 }
491 4 => {
492 for chunk in image.data.chunks_exact(4) {
493 let r = chunk[0];
494 let g = chunk[1];
495 let b = chunk[2];
496 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
497 expected.extend_from_slice(&[255, 255, 255, alpha]);
498 }
499 }
500 _ => expected.resize(width * height * 4, 255),
501 }
502
503 assert_eq!(converted, expected);
504 }
505
506 use super::should_render_as_symbol;
507
508 #[test]
509 fn test_dingbats_are_symbols() {
510 assert!(
512 should_render_as_symbol('\u{2733}'),
513 "✳ EIGHT SPOKED ASTERISK"
514 );
515 assert!(
516 should_render_as_symbol('\u{2734}'),
517 "✴ EIGHT POINTED BLACK STAR"
518 );
519 assert!(should_render_as_symbol('\u{2747}'), "❇ SPARKLE");
520 assert!(should_render_as_symbol('\u{2744}'), "❄ SNOWFLAKE");
521 assert!(should_render_as_symbol('\u{2702}'), "✂ SCISSORS");
522 assert!(should_render_as_symbol('\u{2714}'), "✔ HEAVY CHECK MARK");
523 assert!(
524 should_render_as_symbol('\u{2716}'),
525 "✖ HEAVY MULTIPLICATION X"
526 );
527 assert!(should_render_as_symbol('\u{2728}'), "✨ SPARKLES");
528 }
529
530 #[test]
531 fn test_misc_symbols_are_symbols() {
532 assert!(should_render_as_symbol('\u{2600}'), "☀ SUN");
534 assert!(should_render_as_symbol('\u{2601}'), "☁ CLOUD");
535 assert!(should_render_as_symbol('\u{263A}'), "☺ SMILING FACE");
536 assert!(should_render_as_symbol('\u{2665}'), "♥ BLACK HEART SUIT");
537 assert!(should_render_as_symbol('\u{2660}'), "♠ BLACK SPADE SUIT");
538 }
539
540 #[test]
541 fn test_misc_symbols_arrows_are_symbols() {
542 assert!(should_render_as_symbol('\u{2B50}'), "⭐ WHITE MEDIUM STAR");
544 assert!(should_render_as_symbol('\u{2B55}'), "⭕ HEAVY LARGE CIRCLE");
545 }
546
547 #[test]
548 fn test_regular_emoji_not_symbols() {
549 assert!(
552 !should_render_as_symbol('\u{1F600}'),
553 "😀 GRINNING FACE should not be a symbol"
554 );
555 assert!(
556 !should_render_as_symbol('\u{1F389}'),
557 "🎉 PARTY POPPER should not be a symbol"
558 );
559 assert!(
560 !should_render_as_symbol('\u{1F44D}'),
561 "👍 THUMBS UP should not be a symbol"
562 );
563 }
564
565 #[test]
566 fn test_regular_chars_not_symbols() {
567 assert!(
569 !should_render_as_symbol('A'),
570 "Letter A should not be a symbol"
571 );
572 assert!(
573 !should_render_as_symbol('*'),
574 "Asterisk should not be a symbol (it's ASCII)"
575 );
576 assert!(
577 !should_render_as_symbol('1'),
578 "Digit 1 should not be a symbol"
579 );
580 }
581}