1use std::collections::HashMap;
11
12use agg_rust::path_storage::PathStorage;
13use agg_rust::rasterizer_cells_aa::CellAa;
14
15use crate::font::parsed::{build_glyph_path, OwnedGlyph, ParsedFont};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct GlyphPathKey {
21 pub font_hash: u64,
22 pub glyph_id: u16,
23 pub ppem: u16,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct GlyphCellKey {
30 pub font_hash: u64,
31 pub glyph_id: u16,
32 pub ppem: u16,
33 pub scale_fixed: u32,
36 pub subpx_x: u8,
38 pub subpx_y: u8,
40}
41
42pub struct CachedGlyph<'a> {
44 pub path: &'a PathStorage,
45 pub is_hinted: bool,
46}
47
48pub struct CachedCells {
52 pub cells: Vec<CellAa>,
53}
54
55const MAX_PATH_ENTRIES: usize = 8192;
58const MAX_CELL_ENTRIES: usize = 16384;
61
62pub struct GlyphCache {
64 paths: HashMap<GlyphPathKey, Option<(PathStorage, bool)>>,
65 cells: HashMap<GlyphCellKey, Option<CachedCells>>,
66}
67
68#[inline]
70fn quantize_subpx(frac: f32) -> u8 {
71 let f = frac - frac.floor();
72 (f * 4.0).min(3.0) as u8
73}
74
75impl GlyphCache {
76 #[must_use]
77 pub fn new() -> Self {
78 Self {
79 paths: HashMap::new(),
80 cells: HashMap::new(),
81 }
82 }
83
84 pub fn paths_len(&self) -> usize { self.paths.len() }
86
87 pub fn cells_len(&self) -> usize { self.cells.len() }
89
90 pub fn get_or_build(
93 &mut self,
94 font_hash: u64,
95 glyph_id: u16,
96 glyph_data: &OwnedGlyph,
97 parsed_font: &ParsedFont,
98 ppem: u16,
99 ) -> Option<CachedGlyph<'_>> {
100 if self.paths.len() >= MAX_PATH_ENTRIES {
101 self.paths.clear();
102 }
103 let key = GlyphPathKey { font_hash, glyph_id, ppem };
104 let entry = self
105 .paths
106 .entry(key)
107 .or_insert_with(|| {
108 if ppem > 0 {
110 if let Some(path) = build_hinted_path(glyph_data, parsed_font, ppem) {
111 return Some((path, true));
112 }
113 }
114 build_glyph_path(glyph_data).map(|p| (p, false))
116 });
117 entry.as_ref().map(|(path, is_hinted)| CachedGlyph {
118 path,
119 is_hinted: *is_hinted,
120 })
121 }
122
123 pub fn get_or_build_cells(
131 &mut self,
132 font_hash: u64,
133 glyph_id: u16,
134 ppem: u16,
135 glyph_x: f32,
136 glyph_y: f32,
137 scale: f32,
138 is_hinted: bool,
139 ) -> Option<(&[CellAa], i32, i32)> {
140 if self.cells.len() >= MAX_CELL_ENTRIES {
141 self.cells.clear();
142 }
143 let subpx_x = if is_hinted { 0 } else { quantize_subpx(glyph_x) };
144 let subpx_y = if is_hinted { 0 } else { quantize_subpx(glyph_y) };
145 debug_assert!(scale >= 0.0 && scale < 65536.0, "scale out of range for fixed-point: {}", scale);
146 let scale_fixed = if is_hinted { 0 } else { (scale * 65536.0) as u32 };
147
148 let cell_key = GlyphCellKey {
149 font_hash, glyph_id, ppem, scale_fixed, subpx_x, subpx_y,
150 };
151
152 let int_x = if is_hinted { glyph_x.round() as i32 } else { glyph_x.floor() as i32 };
154 let int_y = if is_hinted { glyph_y.round() as i32 } else { glyph_y.floor() as i32 };
155
156 if !self.cells.contains_key(&cell_key) {
157 let path_key = GlyphPathKey { font_hash, glyph_id, ppem };
159 let path_entry = self.paths.get(&path_key);
160 let cached_cells = path_entry.and_then(|entry| {
161 let (path, _) = entry.as_ref()?;
162 let frac_x = (subpx_x as f64) * 0.25;
163 let frac_y = (subpx_y as f64) * 0.25;
164
165 use agg_rust::trans_affine::TransAffine;
166 use agg_rust::basics::FillingRule;
167 use agg_rust::rasterizer_scanline_aa::RasterizerScanlineAa;
168
169 let mut ras = RasterizerScanlineAa::new();
170 ras.filling_rule(FillingRule::NonZero);
171
172 let transform = if is_hinted {
173 TransAffine::new_translation(frac_x, frac_y)
174 } else {
175 let mut t = TransAffine::new_scaling_uniform(scale as f64);
176 t.multiply(&TransAffine::new_translation(frac_x, frac_y));
177 t
178 };
179
180 let verts = path.vertices();
181 ras.add_path_vertices_transformed(verts, &transform);
182 let cells = ras.outline_cells();
183 if cells.is_empty() { None } else { Some(CachedCells { cells }) }
184 });
185 self.cells.insert(cell_key, cached_cells);
186 }
187
188 let entry = self.cells.get(&cell_key)?;
189 entry.as_ref().map(|cc| (cc.cells.as_slice(), int_x, int_y))
190 }
191
192 pub fn clear(&mut self) {
194 self.paths.clear();
195 self.cells.clear();
196 }
197
198 pub fn evict_if_needed(&mut self) {
202 if self.paths.len() >= MAX_PATH_ENTRIES {
203 self.paths.clear();
204 }
205 if self.cells.len() >= MAX_CELL_ENTRIES {
206 self.cells.clear();
207 }
208 }
209
210 pub fn is_empty(&self) -> bool {
212 self.paths.is_empty()
213 }
214
215 pub fn len(&self) -> usize {
217 self.paths.len()
218 }
219
220 pub fn cell_cache_len(&self) -> usize {
222 self.cells.len()
223 }
224}
225
226fn build_hinted_path(
231 glyph: &OwnedGlyph,
232 parsed_font: &ParsedFont,
233 ppem: u16,
234) -> Option<PathStorage> {
235 let raw_points = glyph.raw_points.as_ref()?;
236 let raw_on_curve = glyph.raw_on_curve.as_ref()?;
237 let raw_contour_ends = glyph.raw_contour_ends.as_ref()?;
238 let instructions = glyph.instructions.as_ref()?;
239
240 if raw_points.is_empty() || raw_contour_ends.is_empty() {
241 return None;
242 }
243
244 let hint_mutex = parsed_font.hint_instance.as_ref()?;
245 let mut hint = hint_mutex.lock().ok()?;
246
247 let upem = parsed_font.font_metrics.units_per_em;
248 if upem == 0 {
249 return None;
250 }
251
252 if hint.set_ppem(ppem, ppem as f64).is_err() {
254 return None;
255 }
256
257 let scale = allsorts::hinting::f26dot6::compute_scale(ppem, upem);
259 use allsorts::hinting::f26dot6::F26Dot6;
260
261 let points_f26dot6: Vec<(i32, i32)> = raw_points
262 .iter()
263 .map(|&(x, y)| {
264 let sx = F26Dot6::from_funits(x as i32, scale);
265 let sy = F26Dot6::from_funits(y as i32, scale);
266 (sx.to_bits(), sy.to_bits())
267 })
268 .collect();
269
270 let adv_f26dot6 = F26Dot6::from_funits(glyph.horz_advance as i32, scale).to_bits();
272
273 let hinted = match hint.hint_glyph_with_orus(
275 &points_f26dot6,
276 Some(raw_points.as_slice()),
277 raw_on_curve,
278 raw_contour_ends,
279 instructions,
280 adv_f26dot6,
281 ) {
282 Ok(h) => h,
283 Err(_) => return None,
284 };
285
286 build_path_from_contours(&hinted, raw_on_curve, raw_contour_ends)
288}
289
290pub fn build_path_from_contours(
299 points: &[(i32, i32)],
300 on_curve: &[bool],
301 contour_ends: &[u16],
302) -> Option<PathStorage> {
303 use agg_rust::basics::PATH_FLAGS_NONE;
304
305 let mut path = PathStorage::new();
306 let mut has_ops = false;
307 let mut contour_start = 0usize;
308
309 for &end_idx in contour_ends {
310 let end = end_idx as usize;
311 if end >= points.len() || contour_start > end {
312 contour_start = end + 1;
313 continue;
314 }
315
316 let pts = &points[contour_start..=end];
317 let flags = &on_curve[contour_start..=end];
318 let n = pts.len();
319 if n < 2 {
320 contour_start = end + 1;
321 continue;
322 }
323
324 let px = |i: usize| -> (f64, f64) {
326 (f26_to_px(pts[i].0) as f64, -f26_to_px(pts[i].1) as f64)
327 };
328 let mid = |a: (f64, f64), b: (f64, f64)| -> (f64, f64) {
329 ((a.0 + b.0) * 0.5, (a.1 + b.1) * 0.5)
330 };
331
332 let (origin, start, until) = if flags[0] {
334 (px(0), 1usize, n)
335 } else if flags[n - 1] {
336 (px(n - 1), 0usize, n - 1)
337 } else {
338 (mid(px(0), px(n - 1)), 0usize, n)
339 };
340
341 path.move_to(origin.0, origin.1);
342 has_ops = true;
343
344 let mut i = start;
345 while i < until {
346 if flags[i] {
347 let to = px(i);
349 path.line_to(to.0, to.1);
350 i += 1;
351 } else {
352 let ctrl = px(i);
354 let next = i + 1;
355 if next < until {
356 if flags[next] {
357 let to = px(next);
359 path.curve3(ctrl.0, ctrl.1, to.0, to.1);
360 i = next + 1;
361 } else {
362 let m = mid(ctrl, px(next));
364 path.curve3(ctrl.0, ctrl.1, m.0, m.1);
365 i = next;
366 }
367 } else {
368 path.curve3(ctrl.0, ctrl.1, origin.0, origin.1);
370 i = next;
371 }
372 }
373 }
374 path.close_polygon(PATH_FLAGS_NONE);
375
376 contour_start = end + 1;
377 }
378
379 if !has_ops {
380 return None;
381 }
382 Some(path)
383}
384
385#[inline]
387fn f26_to_px(v: i32) -> f32 {
388 v as f32 / 64.0
389}