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 image = Render::new(&sources)
177 .format(render_format)
178 .render(&mut scaler, glyph_id)?;
179
180 let (pixels, is_colored) = match image.content {
181 Content::Color => {
182 if force_monochrome {
186 let pixels = convert_color_to_alpha_mask(&image);
187 (pixels, false)
188 } else {
189 (image.data.clone(), true)
190 }
191 }
192 Content::Mask => {
193 let mut pixels = Vec::with_capacity(image.data.len() * 4);
194 for &mask in &image.data {
195 let alpha = if !self.font_antialias {
197 if mask > 127 { 255 } else { 0 }
198 } else {
199 mask
200 };
201 pixels.extend_from_slice(&[255, 255, 255, alpha]);
202 }
203 (pixels, false)
204 }
205 Content::SubpixelMask => {
206 let pixels = convert_subpixel_mask_to_rgba(&image);
207 (pixels, false)
208 }
209 };
210
211 Some(RasterizedGlyph {
212 width: image.placement.width,
213 height: image.placement.height,
214 bearing_x: image.placement.left as f32,
215 bearing_y: image.placement.top as f32,
216 pixels,
217 is_colored,
218 })
219 }
220
221 pub(crate) fn upload_glyph(&mut self, _key: u64, raster: &RasterizedGlyph) -> GlyphInfo {
222 let padding = 2;
223 if self.atlas_next_x + raster.width + padding > 2048 {
224 self.atlas_next_x = 0;
225 self.atlas_next_y += self.atlas_row_height + padding;
226 self.atlas_row_height = 0;
227 }
228
229 if self.atlas_next_y + raster.height + padding > 2048 {
230 self.clear_glyph_cache();
231 }
232
233 let info = GlyphInfo {
234 key: _key,
235 x: self.atlas_next_x,
236 y: self.atlas_next_y,
237 width: raster.width,
238 height: raster.height,
239 bearing_x: raster.bearing_x,
240 bearing_y: raster.bearing_y,
241 is_colored: raster.is_colored,
242 prev: None,
243 next: None,
244 };
245
246 self.queue.write_texture(
247 wgpu::TexelCopyTextureInfo {
248 texture: &self.atlas_texture,
249 mip_level: 0,
250 origin: wgpu::Origin3d {
251 x: info.x,
252 y: info.y,
253 z: 0,
254 },
255 aspect: wgpu::TextureAspect::All,
256 },
257 &raster.pixels,
258 wgpu::TexelCopyBufferLayout {
259 offset: 0,
260 bytes_per_row: Some(4 * raster.width),
261 rows_per_image: Some(raster.height),
262 },
263 wgpu::Extent3d {
264 width: raster.width,
265 height: raster.height,
266 depth_or_array_layers: 1,
267 },
268 );
269
270 self.atlas_next_x += raster.width + padding;
271 self.atlas_row_height = self.atlas_row_height.max(raster.height);
272
273 info
274 }
275}
276
277fn convert_subpixel_mask_to_rgba(image: &swash::scale::image::Image) -> Vec<u8> {
282 let width = image.placement.width as usize;
283 let height = image.placement.height as usize;
284 let mut pixels = Vec::with_capacity(width * height * 4);
285
286 let stride = if width > 0 && height > 0 {
287 image.data.len() / (width * height)
288 } else {
289 0
290 };
291
292 match stride {
293 3 => {
294 for chunk in image.data.chunks_exact(3) {
295 let r = chunk[0];
296 let g = chunk[1];
297 let b = chunk[2];
298 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 4 => {
303 for chunk in image.data.chunks_exact(4) {
304 let r = chunk[0];
305 let g = chunk[1];
306 let b = chunk[2];
307 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
309 pixels.extend_from_slice(&[255, 255, 255, alpha]);
310 }
311 }
312 _ => {
313 pixels.resize(width * height * 4, 255);
315 }
316 }
317
318 pixels
319}
320
321fn convert_color_to_alpha_mask(image: &swash::scale::image::Image) -> Vec<u8> {
330 let width = image.placement.width as usize;
331 let height = image.placement.height as usize;
332 let mut pixels = Vec::with_capacity(width * height * 4);
333
334 for chunk in image.data.chunks_exact(4) {
336 let r = chunk[0];
337 let g = chunk[1];
338 let b = chunk[2];
339 let a = chunk[3];
340
341 let luminance = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
344 let alpha = ((luminance as u32 * a as u32) / 255) as u8;
346
347 pixels.extend_from_slice(&[255, 255, 255, alpha]);
348 }
349
350 pixels
351}
352
353#[cfg(test)]
354mod tests {
355 use super::convert_subpixel_mask_to_rgba;
356 use swash::scale::{Render, ScaleContext, Source};
357 use swash::zeno::Format;
358
359 #[test]
360 fn subpixel_mask_uses_rgba_stride() {
361 let data = std::fs::read("../par-term-fonts/fonts/DejaVuSansMono.ttf").expect("font file");
362 let font = swash::FontRef::from_index(&data, 0).expect("font ref");
363 let mut context = ScaleContext::new();
364 let glyph_id = font.charmap().map('a');
365 let mut scaler = context.builder(font).size(18.0).hint(true).build();
366
367 let image = Render::new(&[
368 Source::ColorOutline(0),
369 Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
370 Source::Outline,
371 Source::Bitmap(swash::scale::StrikeWith::BestFit),
372 ])
373 .format(Format::Subpixel)
374 .render(&mut scaler, glyph_id)
375 .expect("render");
376
377 let converted = convert_subpixel_mask_to_rgba(&image);
378
379 let width = image.placement.width as usize;
380 let height = image.placement.height as usize;
381 let mut expected = Vec::with_capacity(width * height * 4);
382 let stride = if width > 0 && height > 0 {
383 image.data.len() / (width * height)
384 } else {
385 0
386 };
387
388 match stride {
389 3 => {
390 for chunk in image.data.chunks_exact(3) {
391 let r = chunk[0];
392 let g = chunk[1];
393 let b = chunk[2];
394 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
395 expected.extend_from_slice(&[255, 255, 255, alpha]);
396 }
397 }
398 4 => {
399 for chunk in image.data.chunks_exact(4) {
400 let r = chunk[0];
401 let g = chunk[1];
402 let b = chunk[2];
403 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
404 expected.extend_from_slice(&[255, 255, 255, alpha]);
405 }
406 }
407 _ => expected.resize(width * height * 4, 255),
408 }
409
410 assert_eq!(converted, expected);
411 }
412
413 use super::should_render_as_symbol;
414
415 #[test]
416 fn test_dingbats_are_symbols() {
417 assert!(
419 should_render_as_symbol('\u{2733}'),
420 "✳ EIGHT SPOKED ASTERISK"
421 );
422 assert!(
423 should_render_as_symbol('\u{2734}'),
424 "✴ EIGHT POINTED BLACK STAR"
425 );
426 assert!(should_render_as_symbol('\u{2747}'), "❇ SPARKLE");
427 assert!(should_render_as_symbol('\u{2744}'), "❄ SNOWFLAKE");
428 assert!(should_render_as_symbol('\u{2702}'), "✂ SCISSORS");
429 assert!(should_render_as_symbol('\u{2714}'), "✔ HEAVY CHECK MARK");
430 assert!(
431 should_render_as_symbol('\u{2716}'),
432 "✖ HEAVY MULTIPLICATION X"
433 );
434 assert!(should_render_as_symbol('\u{2728}'), "✨ SPARKLES");
435 }
436
437 #[test]
438 fn test_misc_symbols_are_symbols() {
439 assert!(should_render_as_symbol('\u{2600}'), "☀ SUN");
441 assert!(should_render_as_symbol('\u{2601}'), "☁ CLOUD");
442 assert!(should_render_as_symbol('\u{263A}'), "☺ SMILING FACE");
443 assert!(should_render_as_symbol('\u{2665}'), "♥ BLACK HEART SUIT");
444 assert!(should_render_as_symbol('\u{2660}'), "♠ BLACK SPADE SUIT");
445 }
446
447 #[test]
448 fn test_misc_symbols_arrows_are_symbols() {
449 assert!(should_render_as_symbol('\u{2B50}'), "⭐ WHITE MEDIUM STAR");
451 assert!(should_render_as_symbol('\u{2B55}'), "⭕ HEAVY LARGE CIRCLE");
452 }
453
454 #[test]
455 fn test_regular_emoji_not_symbols() {
456 assert!(
459 !should_render_as_symbol('\u{1F600}'),
460 "😀 GRINNING FACE should not be a symbol"
461 );
462 assert!(
463 !should_render_as_symbol('\u{1F389}'),
464 "🎉 PARTY POPPER should not be a symbol"
465 );
466 assert!(
467 !should_render_as_symbol('\u{1F44D}'),
468 "👍 THUMBS UP should not be a symbol"
469 );
470 }
471
472 #[test]
473 fn test_regular_chars_not_symbols() {
474 assert!(
476 !should_render_as_symbol('A'),
477 "Letter A should not be a symbol"
478 );
479 assert!(
480 !should_render_as_symbol('*'),
481 "Asterisk should not be a symbol (it's ASCII)"
482 );
483 assert!(
484 !should_render_as_symbol('1'),
485 "Digit 1 should not be a symbol"
486 );
487 }
488}