1use crate::kurbo::{Affine, BezPath, Vec2};
7use crate::peniko::FontData;
8use alloc::boxed::Box;
9use alloc::vec::Vec;
10use core::fmt::{Debug, Formatter};
11use hashbrown::hash_map::{Entry, RawEntryMut};
12use hashbrown::{Equivalent, HashMap};
13use skrifa::instance::{LocationRef, Size};
14use skrifa::outline::{DrawSettings, OutlineGlyphFormat};
15use skrifa::raw::TableProvider;
16use skrifa::{FontRef, OutlineGlyphCollection};
17use skrifa::{
18 GlyphId, MetadataProvider,
19 outline::{HintingInstance, HintingOptions, OutlinePen},
20};
21
22use crate::colr::convert_bounding_box;
23use crate::encode::x_y_advances;
24use crate::kurbo::Rect;
25use crate::pixmap::Pixmap;
26use skrifa::bitmap::{BitmapData, BitmapFormat, BitmapStrikes, Origin};
27
28#[cfg(not(feature = "std"))]
29use peniko::kurbo::common::FloatFuncs as _;
30
31#[derive(Copy, Clone, Default, Debug)]
33pub struct Glyph {
34 pub id: u32,
39 pub x: f32,
41 pub y: f32,
43}
44
45#[derive(Debug)]
47pub enum GlyphType<'a> {
48 Outline(OutlineGlyph<'a>),
50 Bitmap(BitmapGlyph),
52 Colr(Box<ColorGlyph<'a>>),
54}
55
56#[derive(Debug)]
58pub struct PreparedGlyph<'a> {
59 pub glyph_type: GlyphType<'a>,
61 pub transform: Affine,
63}
64
65#[derive(Debug)]
67pub struct OutlineGlyph<'a> {
68 pub path: &'a BezPath,
70}
71
72#[derive(Debug)]
74pub struct BitmapGlyph {
75 pub pixmap: Pixmap,
77 pub area: Rect,
79}
80
81pub struct ColorGlyph<'a> {
87 pub(crate) skrifa_glyph: skrifa::color::ColorGlyph<'a>,
88 pub(crate) location: LocationRef<'a>,
89 pub(crate) font_ref: &'a FontRef<'a>,
90 pub(crate) draw_transform: Affine,
91 pub area: Rect,
94 pub pix_width: u16,
96 pub pix_height: u16,
98}
99
100impl Debug for ColorGlyph<'_> {
101 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
102 write!(f, "ColorGlyph")
103 }
104}
105
106pub trait GlyphRenderer {
108 fn fill_glyph(&mut self, glyph: PreparedGlyph<'_>);
110
111 fn stroke_glyph(&mut self, glyph: PreparedGlyph<'_>);
113
114 fn take_glyph_caches(&mut self) -> GlyphCaches;
118
119 fn restore_glyph_caches(&mut self, caches: GlyphCaches);
123}
124
125#[derive(Debug)]
127#[must_use = "Methods on the builder don't do anything until `render` is called."]
128pub struct GlyphRunBuilder<'a, T: GlyphRenderer + 'a> {
129 run: GlyphRun<'a>,
130 renderer: &'a mut T,
131}
132
133impl<'a, T: GlyphRenderer + 'a> GlyphRunBuilder<'a, T> {
134 pub fn new(font: FontData, transform: Affine, renderer: &'a mut T) -> Self {
136 Self {
137 run: GlyphRun {
138 font,
139 font_size: 16.0,
140 transform,
141 glyph_transform: None,
142 hint: true,
143 normalized_coords: &[],
144 },
145 renderer,
146 }
147 }
148
149 pub fn font_size(mut self, size: f32) -> Self {
151 self.run.font_size = size;
152 self
153 }
154
155 pub fn glyph_transform(mut self, transform: Affine) -> Self {
158 self.run.glyph_transform = Some(transform);
159 self
160 }
161
162 pub fn hint(mut self, hint: bool) -> Self {
167 self.run.hint = hint;
168 self
169 }
170
171 pub fn normalized_coords(mut self, coords: &'a [NormalizedCoord]) -> Self {
173 self.run.normalized_coords = bytemuck::cast_slice(coords);
174 self
175 }
176
177 pub fn fill_glyphs(self, glyphs: impl Iterator<Item = Glyph>) {
179 self.render(glyphs, Style::Fill);
180 }
181
182 pub fn stroke_glyphs(self, glyphs: impl Iterator<Item = Glyph>) {
184 self.render(glyphs, Style::Stroke);
185 }
186
187 fn render(self, glyphs: impl Iterator<Item = Glyph>, style: Style) {
188 let font_ref =
189 FontRef::from_index(self.run.font.data.as_ref(), self.run.font.index).unwrap();
190
191 let upem: f32 = font_ref.head().map(|h| h.units_per_em()).unwrap().into();
192
193 let outlines = font_ref.outline_glyphs();
194 let color_glyphs = font_ref.color_glyphs();
195 let bitmaps = font_ref.bitmap_strikes();
196
197 let GlyphCaches {
199 mut hinting_cache,
200 mut outline_cache,
201 } = self.renderer.take_glyph_caches();
202 let mut outline_cache_session =
203 OutlineCacheSession::new(&mut outline_cache, VarLookupKey(self.run.normalized_coords));
204 let PreparedGlyphRun {
205 transform: initial_transform,
206 size,
207 normalized_coords,
208 hinting_instance,
209 } = prepare_glyph_run(&self.run, &outlines, &mut hinting_cache);
210
211 let render_glyph = match style {
212 Style::Fill => GlyphRenderer::fill_glyph,
213 Style::Stroke => GlyphRenderer::stroke_glyph,
214 };
215
216 for glyph in glyphs {
217 let bitmap_data = bitmaps
218 .glyph_for_size(Size::new(self.run.font_size), GlyphId::new(glyph.id))
219 .and_then(|g| match g.data {
220 #[cfg(feature = "png")]
221 BitmapData::Png(data) => Pixmap::from_png(data).ok().map(|d| (g, d)),
222 #[cfg(not(feature = "png"))]
223 BitmapData::Png(_) => None,
224 BitmapData::Bgra(_) => None,
227 BitmapData::Mask(_) => None,
228 });
229
230 let (glyph_type, transform) =
231 if let Some(color_glyph) = color_glyphs.get(GlyphId::new(glyph.id)) {
232 prepare_colr_glyph(
233 &font_ref,
234 glyph,
235 self.run.font_size,
236 upem,
237 initial_transform,
238 color_glyph,
239 normalized_coords,
240 )
241 } else if let Some((bitmap_glyph, pixmap)) = bitmap_data {
242 prepare_bitmap_glyph(
243 &bitmaps,
244 glyph,
245 pixmap,
246 self.run.font_size,
247 upem,
248 initial_transform,
249 bitmap_glyph,
250 )
251 } else {
252 let Some(outline) = outlines.get(GlyphId::new(glyph.id)) else {
253 continue;
254 };
255
256 prepare_outline_glyph(
257 glyph,
258 self.run.font.data.id(),
259 self.run.font.index,
260 &mut outline_cache_session,
261 size,
262 initial_transform,
263 self.run.transform,
264 &outline,
265 hinting_instance,
266 normalized_coords,
267 )
268 };
269
270 let prepared_glyph = PreparedGlyph {
271 glyph_type,
272 transform,
273 };
274
275 render_glyph(self.renderer, prepared_glyph);
276 }
277
278 self.renderer.restore_glyph_caches(GlyphCaches {
279 outline_cache,
280 hinting_cache,
281 });
282 }
283}
284
285fn prepare_outline_glyph<'a>(
286 glyph: Glyph,
287 font_id: u64,
288 font_index: u32,
289 outline_cache: &'a mut OutlineCacheSession<'_>,
290 size: Size,
291 initial_transform: Affine,
293 run_transform: Affine,
295 outline_glyph: &skrifa::outline::OutlineGlyph<'a>,
296 hinting_instance: Option<&HintingInstance>,
297 normalized_coords: &[skrifa::instance::NormalizedCoord],
298) -> (GlyphType<'a>, Affine) {
299 let path = outline_cache.get_or_insert(
300 glyph.id,
301 font_id,
302 font_index,
303 size,
304 VarLookupKey(normalized_coords),
305 outline_glyph,
306 hinting_instance,
307 );
308
309 let [a, b, c, d, _, _] = run_transform.as_coeffs();
316 let translation = Vec2::new(
317 a * f64::from(glyph.x) + c * f64::from(glyph.y),
318 b * f64::from(glyph.x) + d * f64::from(glyph.y),
319 );
320
321 let mut final_transform = initial_transform
324 .then_translate(translation)
325 .pre_scale_non_uniform(1.0, -1.0)
328 .as_coeffs();
329
330 if hinting_instance.is_some() {
331 final_transform[5] = final_transform[5].round();
332 }
333
334 (
335 GlyphType::Outline(OutlineGlyph { path: &path.0 }),
336 Affine::new(final_transform),
337 )
338}
339
340fn prepare_bitmap_glyph<'a>(
341 bitmaps: &BitmapStrikes<'_>,
342 glyph: Glyph,
343 pixmap: Pixmap,
344 font_size: f32,
345 upem: f32,
346 initial_transform: Affine,
347 bitmap_glyph: skrifa::bitmap::BitmapGlyph<'a>,
348) -> (GlyphType<'a>, Affine) {
349 let x_scale_factor = font_size / bitmap_glyph.ppem_x;
350 let y_scale_factor = font_size / bitmap_glyph.ppem_y;
351 let font_units_to_size = font_size / upem;
352
353 let bearing_y = if bitmap_glyph.bearing_y == 0.0 && bitmaps.format() == Some(BitmapFormat::Sbix)
359 {
360 100.0
361 } else {
362 bitmap_glyph.bearing_y
363 };
364
365 let origin_shift = match bitmap_glyph.placement_origin {
366 Origin::TopLeft => Vec2::default(),
367 Origin::BottomLeft => Vec2 {
368 x: 0.,
369 y: -f64::from(pixmap.height()),
370 },
371 };
372
373 let transform = initial_transform
374 .pre_translate(Vec2::new(glyph.x.into(), glyph.y.into()))
375 .pre_translate(Vec2 {
377 x: (-bitmap_glyph.bearing_x * font_units_to_size).into(),
378 y: (bearing_y * font_units_to_size).into(),
379 })
380 .pre_scale_non_uniform(f64::from(x_scale_factor), f64::from(y_scale_factor))
382 .pre_translate(Vec2 {
384 x: (-bitmap_glyph.inner_bearing_x).into(),
385 y: (-bitmap_glyph.inner_bearing_y).into(),
386 })
387 .pre_translate(origin_shift);
388
389 let area = Rect::new(
392 0.0,
393 0.0,
394 f64::from(pixmap.width()),
395 f64::from(pixmap.height()),
396 );
397
398 (GlyphType::Bitmap(BitmapGlyph { pixmap, area }), transform)
399}
400
401fn prepare_colr_glyph<'a>(
402 font_ref: &'a FontRef<'a>,
403 glyph: Glyph,
404 font_size: f32,
405 upem: f32,
406 run_transform: Affine,
407 color_glyph: skrifa::color::ColorGlyph<'a>,
408 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
409) -> (GlyphType<'a>, Affine) {
410 let scale = font_size / upem;
430
431 let transform = run_transform.pre_translate(Vec2::new(glyph.x.into(), glyph.y.into()));
432
433 let scale_factor = {
438 let (x_vec, y_vec) = x_y_advances(&transform.pre_scale(f64::from(scale)));
439 x_vec.length().max(y_vec.length())
440 };
441
442 let bbox = color_glyph
443 .bounding_box(LocationRef::default(), Size::unscaled())
444 .map(convert_bounding_box)
445 .unwrap_or(Rect::new(0.0, 0.0, f64::from(upem), f64::from(upem)));
446
447 let scaled_bbox = bbox.scale_from_origin(scale_factor);
450
451 let glyph_transform = transform
452 * Affine::scale_non_uniform(1.0, -1.0)
462 * Affine::translate((scaled_bbox.x0, scaled_bbox.y0));
465
466 let (pix_width, pix_height) = (
467 scaled_bbox.width().ceil() as u16,
468 scaled_bbox.height().ceil() as u16,
469 );
470
471 let draw_transform =
472 Affine::translate((-scaled_bbox.x0, -scaled_bbox.y0)) *
475 Affine::scale(scale_factor);
477
478 let area = Rect::new(0.0, 0.0, scaled_bbox.width(), scaled_bbox.height());
481
482 (
483 GlyphType::Colr(Box::new(ColorGlyph {
484 skrifa_glyph: color_glyph,
485 font_ref,
486 location: LocationRef::new(normalized_coords),
487 area,
488 pix_width,
489 pix_height,
490 draw_transform,
491 })),
492 glyph_transform,
493 )
494}
495
496#[derive(Debug, Clone, Copy, PartialEq, Eq)]
498pub enum Style {
499 Fill,
501 Stroke,
503}
504
505#[derive(Clone, Debug)]
507struct GlyphRun<'a> {
508 font: FontData,
510 font_size: f32,
512 transform: Affine,
514 glyph_transform: Option<Affine>,
517 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
519 hint: bool,
521}
522
523struct PreparedGlyphRun<'a> {
524 transform: Affine,
527 size: Size,
529 normalized_coords: &'a [skrifa::instance::NormalizedCoord],
530 hinting_instance: Option<&'a HintingInstance>,
531}
532
533fn prepare_glyph_run<'a>(
538 run: &GlyphRun<'a>,
539 outlines: &OutlineGlyphCollection<'_>,
540 hint_cache: &'a mut HintCache,
541) -> PreparedGlyphRun<'a> {
542 if !run.hint {
543 return PreparedGlyphRun {
544 transform: run.transform * run.glyph_transform.unwrap_or(Affine::IDENTITY),
545 size: Size::new(run.font_size),
546 normalized_coords: run.normalized_coords,
547 hinting_instance: None,
548 };
549 }
550
551 let total_transform = run.transform * run.glyph_transform.unwrap_or(Affine::IDENTITY);
562 let [t_a, t_b, t_c, t_d, t_e, t_f] = total_transform.as_coeffs();
563
564 let uniform_scale = t_a == t_d;
565 let vertically_uniform = t_b == 0.;
566
567 if uniform_scale && vertically_uniform {
568 let vertical_font_size = run.font_size * t_d as f32;
569 let size = Size::new(vertical_font_size);
570
571 let hinting_instance = hint_cache.get(&HintKey {
572 font_id: run.font.data.id(),
573 font_index: run.font.index,
574 outlines,
575 size,
576 coords: run.normalized_coords,
577 });
578
579 PreparedGlyphRun {
580 transform: Affine::new([1., 0., t_c, 1., t_e, t_f]),
581 size,
582 normalized_coords: run.normalized_coords,
583 hinting_instance,
584 }
585 } else {
586 PreparedGlyphRun {
587 transform: total_transform,
588 size: Size::new(run.font_size),
589 normalized_coords: run.normalized_coords,
590 hinting_instance: None,
591 }
592 }
593}
594
595const HINTING_OPTIONS: HintingOptions = HintingOptions {
598 engine: skrifa::outline::Engine::AutoFallback,
599 target: skrifa::outline::Target::Smooth {
600 mode: skrifa::outline::SmoothMode::Lcd,
601 symmetric_rendering: false,
602 preserve_linear_metrics: true,
603 },
604};
605
606#[derive(Clone, Default)]
607pub(crate) struct OutlinePath(pub(crate) BezPath);
608
609impl OutlinePath {
610 pub(crate) fn new() -> Self {
611 Self(BezPath::new())
612 }
613}
614
615impl OutlinePen for OutlinePath {
617 #[inline]
618 fn move_to(&mut self, x: f32, y: f32) {
619 self.0.move_to((x, y));
620 }
621
622 #[inline]
623 fn line_to(&mut self, x: f32, y: f32) {
624 self.0.line_to((x, y));
625 }
626
627 #[inline]
628 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
629 self.0.curve_to((cx0, cy0), (cx1, cy1), (x, y));
630 }
631
632 #[inline]
633 fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
634 self.0.quad_to((cx, cy), (x, y));
635 }
636
637 #[inline]
638 fn close(&mut self) {
639 self.0.close_path();
640 }
641}
642
643pub type NormalizedCoord = i16;
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657
658 const _NORMALISED_COORD_SIZE_MATCHES: () =
659 assert!(size_of::<skrifa::instance::NormalizedCoord>() == size_of::<NormalizedCoord>());
660}
661
662#[derive(Debug, Default)]
665pub struct GlyphCaches {
666 outline_cache: OutlineCache,
667 hinting_cache: HintCache,
668}
669
670impl GlyphCaches {
671 pub fn new() -> Self {
673 Default::default()
674 }
675
676 pub fn clear(&mut self) {
678 self.outline_cache.clear();
679 self.hinting_cache.clear();
680 }
681
682 pub fn maintain(&mut self) {
686 self.outline_cache.maintain();
687 }
688}
689
690#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, Debug)]
691struct OutlineKey {
692 font_id: u64,
693 font_index: u32,
694 glyph_id: u32,
695 size_bits: u32,
696 hint: bool,
697}
698
699struct OutlineEntry {
700 path: OutlinePath,
701 serial: u32,
702}
703
704impl OutlineEntry {
705 const fn new(path: OutlinePath, serial: u32) -> Self {
706 Self { path, serial }
707 }
708}
709
710#[derive(Default)]
713struct OutlineCache {
714 free_list: Vec<OutlinePath>,
715 static_map: HashMap<OutlineKey, OutlineEntry>,
716 variable_map: HashMap<VarKey, HashMap<OutlineKey, OutlineEntry>>,
717 cached_count: usize,
718 serial: u32,
719 last_prune_serial: u32,
720}
721
722impl Debug for OutlineCache {
723 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
724 f.debug_struct("OutlineCache")
725 .field("free_list", &self.free_list.len())
726 .field("static_map", &self.static_map.len())
727 .field("variable_map", &self.variable_map.len())
728 .field("cached_count", &self.cached_count)
729 .field("serial", &self.serial)
730 .field("last_prune_serial", &self.last_prune_serial)
731 .finish()
732 }
733}
734
735impl OutlineCache {
736 fn maintain(&mut self) {
737 const MAX_ENTRY_AGE: u32 = 64;
739 const PRUNE_FREQUENCY: u32 = 64;
741 const CACHED_COUNT_THRESHOLD: usize = 256;
743 const MAX_FREE_LIST_SIZE: usize = 128;
745
746 let free_list = &mut self.free_list;
747 let serial = self.serial;
748 self.serial += 1;
749 if serial - self.last_prune_serial < PRUNE_FREQUENCY
751 && self.cached_count < CACHED_COUNT_THRESHOLD
752 {
753 return;
754 }
755 self.last_prune_serial = serial;
756 self.static_map.retain(|_, entry| {
757 if serial - entry.serial > MAX_ENTRY_AGE {
758 if free_list.len() < MAX_FREE_LIST_SIZE {
759 free_list.push(core::mem::take(&mut entry.path));
760 }
761 self.cached_count -= 1;
762 false
763 } else {
764 true
765 }
766 });
767 self.variable_map.retain(|_, map| {
768 map.retain(|_, entry| {
769 if serial - entry.serial > MAX_ENTRY_AGE {
770 if free_list.len() < MAX_FREE_LIST_SIZE {
771 free_list.push(core::mem::take(&mut entry.path));
772 }
773 self.cached_count -= 1;
774 false
775 } else {
776 true
777 }
778 });
779 !map.is_empty()
780 });
781 }
782
783 fn clear(&mut self) {
784 self.free_list.clear();
785 self.static_map.clear();
786 self.variable_map.clear();
787 self.cached_count = 0;
788 self.serial = 0;
789 self.last_prune_serial = 0;
790 }
791}
792
793struct OutlineCacheSession<'a> {
794 map: &'a mut HashMap<OutlineKey, OutlineEntry>,
795 free_list: &'a mut Vec<OutlinePath>,
796 serial: u32,
797 cached_count: &'a mut usize,
798}
799
800impl<'a> OutlineCacheSession<'a> {
801 fn new(outline_cache: &'a mut OutlineCache, var_key: VarLookupKey<'_>) -> Self {
802 let map = if var_key.0.is_empty() {
803 &mut outline_cache.static_map
804 } else {
805 match outline_cache
806 .variable_map
807 .raw_entry_mut()
808 .from_key(&var_key)
809 {
810 RawEntryMut::Occupied(entry) => entry.into_mut(),
811 RawEntryMut::Vacant(entry) => entry.insert(var_key.into(), HashMap::new()).1,
812 }
813 };
814 Self {
815 map,
816 free_list: &mut outline_cache.free_list,
817 serial: outline_cache.serial,
818 cached_count: &mut outline_cache.cached_count,
819 }
820 }
821
822 fn get_or_insert(
823 &mut self,
824 glyph_id: u32,
825 font_id: u64,
826 font_index: u32,
827 size: Size,
828 var_key: VarLookupKey<'_>,
829 outline_glyph: &skrifa::outline::OutlineGlyph<'_>,
830 hinting_instance: Option<&HintingInstance>,
831 ) -> &OutlinePath {
832 let key = OutlineKey {
833 glyph_id,
834 font_id,
835 font_index,
836 size_bits: size.ppem().unwrap().to_bits(),
837 hint: hinting_instance.is_some(),
838 };
839
840 match self.map.entry(key) {
841 Entry::Occupied(mut entry) => {
842 entry.get_mut().serial = self.serial;
843 &entry.into_mut().path
844 }
845 Entry::Vacant(entry) => {
846 let mut path = self.free_list.pop().unwrap_or_default();
847
848 let draw_settings = if let Some(hinting_instance) = hinting_instance {
849 DrawSettings::hinted(hinting_instance, false)
850 } else {
851 DrawSettings::unhinted(size, var_key.0)
852 };
853
854 path.0.truncate(0);
855 outline_glyph.draw(draw_settings, &mut path).unwrap();
856
857 let entry = entry.insert(OutlineEntry::new(path, self.serial));
858 *self.cached_count += 1;
859 &entry.path
860 }
861 }
862 }
863}
864
865type VarKey = Vec<skrifa::instance::NormalizedCoord>;
867
868#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
870struct VarLookupKey<'a>(&'a [skrifa::instance::NormalizedCoord]);
871
872impl Equivalent<VarKey> for VarLookupKey<'_> {
873 fn equivalent(&self, other: &VarKey) -> bool {
874 self.0 == *other
875 }
876}
877
878impl From<VarLookupKey<'_>> for VarKey {
879 fn from(key: VarLookupKey<'_>) -> Self {
880 key.0.to_vec()
881 }
882}
883
884const MAX_CACHED_HINT_INSTANCES: usize = 16;
888
889struct HintKey<'a> {
890 font_id: u64,
891 font_index: u32,
892 outlines: &'a OutlineGlyphCollection<'a>,
893 size: Size,
894 coords: &'a [skrifa::instance::NormalizedCoord],
895}
896
897impl HintKey<'_> {
898 fn instance(&self) -> Option<HintingInstance> {
899 HintingInstance::new(self.outlines, self.size, self.coords, HINTING_OPTIONS).ok()
900 }
901}
902
903#[derive(Default)]
907struct HintCache {
908 glyf_entries: Vec<HintEntry>,
911 cff_entries: Vec<HintEntry>,
912 serial: u64,
913}
914
915impl Debug for HintCache {
916 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
917 f.debug_struct("HintCache")
918 .field("glyf_entries", &self.glyf_entries.len())
919 .field("cff_entries", &self.cff_entries.len())
920 .field("serial", &self.serial)
921 .finish()
922 }
923}
924
925impl HintCache {
926 fn get(&mut self, key: &HintKey<'_>) -> Option<&HintingInstance> {
927 let entries = match key.outlines.format()? {
928 OutlineGlyphFormat::Glyf => &mut self.glyf_entries,
929 OutlineGlyphFormat::Cff | OutlineGlyphFormat::Cff2 => &mut self.cff_entries,
930 };
931 let (entry_ix, is_current) = find_hint_entry(entries, key)?;
932 let entry = entries.get_mut(entry_ix)?;
933 self.serial += 1;
934 entry.serial = self.serial;
935 if !is_current {
936 entry.font_id = key.font_id;
937 entry.font_index = key.font_index;
938 entry
939 .instance
940 .reconfigure(key.outlines, key.size, key.coords, HINTING_OPTIONS)
941 .ok()?;
942 }
943 Some(&entry.instance)
944 }
945
946 fn clear(&mut self) {
947 self.glyf_entries.clear();
948 self.cff_entries.clear();
949 self.serial = 0;
950 }
951}
952
953struct HintEntry {
954 font_id: u64,
955 font_index: u32,
956 instance: HintingInstance,
957 serial: u64,
958}
959
960fn find_hint_entry(entries: &mut Vec<HintEntry>, key: &HintKey<'_>) -> Option<(usize, bool)> {
961 let mut found_serial = u64::MAX;
962 let mut found_index = 0;
963 for (ix, entry) in entries.iter().enumerate() {
964 if entry.font_id == key.font_id
965 && entry.font_index == key.font_index
966 && entry.instance.size() == key.size
967 && entry.instance.location().coords() == key.coords
968 {
969 return Some((ix, true));
970 }
971 if entry.serial < found_serial {
972 found_serial = entry.serial;
973 found_index = ix;
974 }
975 }
976 if entries.len() < MAX_CACHED_HINT_INSTANCES {
977 let instance = key.instance()?;
978 let ix = entries.len();
979 entries.push(HintEntry {
980 font_id: key.font_id,
981 font_index: key.font_index,
982 instance,
983 serial: 0,
985 });
986 Some((ix, true))
987 } else {
988 Some((found_index, false))
989 }
990}