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
14impl CellRenderer {
15 pub fn clear_glyph_cache(&mut self) {
16 self.glyph_cache.clear();
17 self.lru_head = None;
18 self.lru_tail = None;
19 self.atlas_next_x = 0;
20 self.atlas_next_y = 0;
21 self.atlas_row_height = 0;
22 self.dirty_rows.fill(true);
23 self.upload_solid_pixel();
25 }
26
27 pub(crate) fn lru_remove(&mut self, key: u64) {
28 let info = self.glyph_cache.get(&key).unwrap();
29 let prev = info.prev;
30 let next = info.next;
31
32 if let Some(p) = prev {
33 self.glyph_cache.get_mut(&p).unwrap().next = next;
34 } else {
35 self.lru_head = next;
36 }
37
38 if let Some(n) = next {
39 self.glyph_cache.get_mut(&n).unwrap().prev = prev;
40 } else {
41 self.lru_tail = prev;
42 }
43 }
44
45 pub(crate) fn lru_push_front(&mut self, key: u64) {
46 let next = self.lru_head;
47 if let Some(n) = next {
48 self.glyph_cache.get_mut(&n).unwrap().prev = Some(key);
49 } else {
50 self.lru_tail = Some(key);
51 }
52
53 let info = self.glyph_cache.get_mut(&key).unwrap();
54 info.prev = None;
55 info.next = next;
56 self.lru_head = Some(key);
57 }
58
59 pub(crate) fn rasterize_glyph(
60 &self,
61 font_idx: usize,
62 glyph_id: u16,
63 ) -> Option<RasterizedGlyph> {
64 let font = self.font_manager.get_font(font_idx)?;
65 use swash::scale::image::Content;
67 use swash::scale::{Render, ScaleContext};
68 use swash::zeno::Format;
69
70 let mut context = ScaleContext::new();
71
72 let mut scaler = context
74 .builder(*font)
75 .size(self.font_size_pixels)
76 .hint(self.font_hinting)
77 .build();
78
79 let use_thin_strokes = self.should_use_thin_strokes();
81 let render_format = if !self.font_antialias {
82 Format::Alpha
84 } else if use_thin_strokes {
85 Format::Subpixel
87 } else {
88 Format::Alpha
90 };
91
92 let image = Render::new(&[
96 swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
97 swash::scale::Source::ColorOutline(0),
98 swash::scale::Source::Outline,
99 ])
100 .format(render_format)
101 .render(&mut scaler, glyph_id)?;
102
103 let (pixels, is_colored) = match image.content {
104 Content::Color => (image.data.clone(), true),
105 Content::Mask => {
106 let mut pixels = Vec::with_capacity(image.data.len() * 4);
107 for &mask in &image.data {
108 let alpha = if !self.font_antialias {
110 if mask > 127 { 255 } else { 0 }
111 } else {
112 mask
113 };
114 pixels.extend_from_slice(&[255, 255, 255, alpha]);
115 }
116 (pixels, false)
117 }
118 Content::SubpixelMask => {
119 let pixels = convert_subpixel_mask_to_rgba(&image);
120 (pixels, false)
121 }
122 };
123
124 Some(RasterizedGlyph {
125 width: image.placement.width,
126 height: image.placement.height,
127 bearing_x: image.placement.left as f32,
128 bearing_y: image.placement.top as f32,
129 pixels,
130 is_colored,
131 })
132 }
133
134 pub(crate) fn upload_glyph(&mut self, _key: u64, raster: &RasterizedGlyph) -> GlyphInfo {
135 let padding = 2;
136 if self.atlas_next_x + raster.width + padding > 2048 {
137 self.atlas_next_x = 0;
138 self.atlas_next_y += self.atlas_row_height + padding;
139 self.atlas_row_height = 0;
140 }
141
142 if self.atlas_next_y + raster.height + padding > 2048 {
143 self.clear_glyph_cache();
144 }
145
146 let info = GlyphInfo {
147 key: _key,
148 x: self.atlas_next_x,
149 y: self.atlas_next_y,
150 width: raster.width,
151 height: raster.height,
152 bearing_x: raster.bearing_x,
153 bearing_y: raster.bearing_y,
154 is_colored: raster.is_colored,
155 prev: None,
156 next: None,
157 };
158
159 self.queue.write_texture(
160 wgpu::TexelCopyTextureInfo {
161 texture: &self.atlas_texture,
162 mip_level: 0,
163 origin: wgpu::Origin3d {
164 x: info.x,
165 y: info.y,
166 z: 0,
167 },
168 aspect: wgpu::TextureAspect::All,
169 },
170 &raster.pixels,
171 wgpu::TexelCopyBufferLayout {
172 offset: 0,
173 bytes_per_row: Some(4 * raster.width),
174 rows_per_image: Some(raster.height),
175 },
176 wgpu::Extent3d {
177 width: raster.width,
178 height: raster.height,
179 depth_or_array_layers: 1,
180 },
181 );
182
183 self.atlas_next_x += raster.width + padding;
184 self.atlas_row_height = self.atlas_row_height.max(raster.height);
185
186 info
187 }
188}
189
190fn convert_subpixel_mask_to_rgba(image: &swash::scale::image::Image) -> Vec<u8> {
195 let width = image.placement.width as usize;
196 let height = image.placement.height as usize;
197 let mut pixels = Vec::with_capacity(width * height * 4);
198
199 let stride = if width > 0 && height > 0 {
200 image.data.len() / (width * height)
201 } else {
202 0
203 };
204
205 match stride {
206 3 => {
207 for chunk in image.data.chunks_exact(3) {
208 let r = chunk[0];
209 let g = chunk[1];
210 let b = chunk[2];
211 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
212 pixels.extend_from_slice(&[255, 255, 255, alpha]);
213 }
214 }
215 4 => {
216 for chunk in image.data.chunks_exact(4) {
217 let r = chunk[0];
218 let g = chunk[1];
219 let b = chunk[2];
220 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
222 pixels.extend_from_slice(&[255, 255, 255, alpha]);
223 }
224 }
225 _ => {
226 pixels.resize(width * height * 4, 255);
228 }
229 }
230
231 pixels
232}
233
234#[cfg(test)]
235mod tests {
236 use super::convert_subpixel_mask_to_rgba;
237 use swash::scale::{Render, ScaleContext, Source};
238 use swash::zeno::Format;
239
240 #[test]
241 fn subpixel_mask_uses_rgba_stride() {
242 let data = std::fs::read("../par-term-fonts/fonts/DejaVuSansMono.ttf").expect("font file");
243 let font = swash::FontRef::from_index(&data, 0).expect("font ref");
244 let mut context = ScaleContext::new();
245 let glyph_id = font.charmap().map('a');
246 let mut scaler = context.builder(font).size(18.0).hint(true).build();
247
248 let image = Render::new(&[
249 Source::ColorOutline(0),
250 Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
251 Source::Outline,
252 Source::Bitmap(swash::scale::StrikeWith::BestFit),
253 ])
254 .format(Format::Subpixel)
255 .render(&mut scaler, glyph_id)
256 .expect("render");
257
258 let converted = convert_subpixel_mask_to_rgba(&image);
259
260 let width = image.placement.width as usize;
261 let height = image.placement.height as usize;
262 let mut expected = Vec::with_capacity(width * height * 4);
263 let stride = if width > 0 && height > 0 {
264 image.data.len() / (width * height)
265 } else {
266 0
267 };
268
269 match stride {
270 3 => {
271 for chunk in image.data.chunks_exact(3) {
272 let r = chunk[0];
273 let g = chunk[1];
274 let b = chunk[2];
275 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
276 expected.extend_from_slice(&[255, 255, 255, alpha]);
277 }
278 }
279 4 => {
280 for chunk in image.data.chunks_exact(4) {
281 let r = chunk[0];
282 let g = chunk[1];
283 let b = chunk[2];
284 let alpha = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
285 expected.extend_from_slice(&[255, 255, 255, alpha]);
286 }
287 }
288 _ => expected.resize(width * height * 4, 255),
289 }
290
291 assert_eq!(converted, expected);
292 }
293}