swash/scale/
hinting_cache.rs

1use alloc::vec::Vec;
2use skrifa::{
3    instance::{NormalizedCoord, Size},
4    outline::{
5        HintingInstance, HintingMode, LcdLayout, OutlineGlyphCollection, OutlineGlyphFormat,
6    },
7};
8
9/// We keep this small to enable a simple LRU cache with a linear
10/// search. Regenerating hinting data is low to medium cost so it's fine
11/// to redo it occasionally.
12const MAX_CACHED_HINT_INSTANCES: usize = 8;
13
14pub(crate) struct HintingKey<'a> {
15    pub id: [u64; 2],
16    pub outlines: &'a OutlineGlyphCollection<'a>,
17    pub size: Size,
18    pub coords: &'a [NormalizedCoord],
19}
20
21impl<'a> HintingKey<'a> {
22    fn new_instance(&self) -> Option<HintingInstance> {
23        HintingInstance::new(self.outlines, self.size, self.coords, HINTING_MODE).ok()
24    }
25}
26
27const HINTING_MODE: HintingMode = HintingMode::Smooth {
28    lcd_subpixel: Some(LcdLayout::Horizontal),
29    preserve_linear_metrics: true,
30};
31
32#[derive(Default)]
33pub(super) struct HintingCache {
34    // Split caches for glyf/cff because the instance type can reuse
35    // internal memory when reconfigured for the same format.
36    glyf_entries: Vec<HintingEntry>,
37    cff_entries: Vec<HintingEntry>,
38    serial: u64,
39}
40
41impl HintingCache {
42    pub(super) fn get(&mut self, key: &HintingKey) -> Option<&HintingInstance> {
43        let entries = match key.outlines.format()? {
44            OutlineGlyphFormat::Glyf => &mut self.glyf_entries,
45            OutlineGlyphFormat::Cff | OutlineGlyphFormat::Cff2 => &mut self.cff_entries,
46        };
47        let (entry_ix, is_current) = find_hinting_entry(entries, key)?;
48        let entry = entries.get_mut(entry_ix)?;
49        self.serial += 1;
50        entry.serial = self.serial;
51        if !is_current {
52            entry.id = key.id;
53            entry
54                .instance
55                .reconfigure(key.outlines, key.size, key.coords, HINTING_MODE)
56                .ok()?;
57        }
58        Some(&entry.instance)
59    }
60}
61
62struct HintingEntry {
63    id: [u64; 2],
64    instance: HintingInstance,
65    serial: u64,
66}
67
68fn find_hinting_entry(entries: &mut Vec<HintingEntry>, key: &HintingKey) -> Option<(usize, bool)> {
69    let mut found_serial = u64::MAX;
70    let mut found_index = 0;
71    for (ix, entry) in entries.iter().enumerate() {
72        if entry.id == key.id
73            && entry.instance.size() == key.size
74            && entry.instance.location().coords() == key.coords
75        {
76            return Some((ix, true));
77        }
78        if entry.serial < found_serial {
79            found_serial = entry.serial;
80            found_index = ix;
81        }
82    }
83    if entries.len() < MAX_CACHED_HINT_INSTANCES {
84        let instance = key.new_instance()?;
85        let ix = entries.len();
86        entries.push(HintingEntry {
87            id: key.id,
88            instance,
89            serial: 0,
90        });
91        Some((ix, true))
92    } else {
93        Some((found_index, false))
94    }
95}