font_index/
context.rs

1use super::family::parse_families;
2use super::index::*;
3use super::library::FontLibrary;
4use super::types::{FamilyId, FamilyKey, FontId, FontKey, SourceId};
5use super::{shared_data::SharedData, Font};
6#[cfg(feature = "emacs")]
7use crate::emacs::FontSpec;
8use crate::util::fxhash::FxHashMap;
9use std::sync::Arc;
10use swash::proxy::CharmapProxy;
11use swash::text::{
12    cluster::{CharCluster, Status},
13    Cjk, Language, Script,
14};
15use swash::{Attributes, Synthesis};
16pub type FontGroupKey = (u64, Attributes);
17type Epoch = u64;
18
19/// Identifier for a cached font group.
20#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
21pub struct FontGroupId(pub u64);
22
23const MAX_INLINE: usize = 6;
24
25pub struct FontContext {
26    library: FontLibrary,
27    fonts: FontCache,
28    groups: GroupCache,
29}
30
31impl FontContext {
32    pub fn new(library: FontLibrary) -> Self {
33        let index = library.inner.index.read().unwrap().clone();
34        let fonts = FontCache {
35            index,
36            sources: FxHashMap::default(),
37            epoch: 0,
38        };
39        Self {
40            library,
41            fonts,
42            groups: GroupCache::default(),
43        }
44    }
45
46    /// Returns the underlying font library.
47    pub fn library(&self) -> &FontLibrary {
48        &self.library
49    }
50
51    /// Resets font group caches and state. This should be called at the end
52    /// of every layout session.
53    pub fn reset_group_state(&mut self) {
54        self.groups.reset();
55    }
56
57    /// Registers a font group.
58    pub fn register_group(&mut self, families: &str, key: u64, attrs: Attributes) -> FontGroupId {
59        self.groups.get(&self.fonts, families, key, attrs)
60    }
61
62    /// Selects a font group for subsequent cluster mapping operations.
63    pub fn select_group(&mut self, descriptor: FontGroupId) {
64        self.groups.select(descriptor);
65    }
66
67    /// Selects fallback fonts for the specified writing system.
68    pub fn select_fallbacks(&mut self, script: Script, language: Option<&Language>) {
69        self.groups
70            .select_fallbacks(script, language.map(|l| l.cjk()).unwrap_or(Cjk::None))
71    }
72
73    /// Maps the characters in a cluster to nominal glyph identifiers and
74    /// returns the most suitable font based on the currently selected
75    /// descriptor and fallback configuration.
76    pub fn map_cluster(
77        &mut self,
78        cluster: &mut CharCluster,
79        synthesis: &mut Synthesis,
80    ) -> Option<Font> {
81        let mut best = None;
82        let list = &self.groups.state.fonts;
83        for entry in self.groups.fonts.get_mut(list.start..list.end)?.iter_mut() {
84            match entry.map_cluster(&mut self.fonts, cluster, synthesis, best.is_none()) {
85                Some((font, status)) => {
86                    if status == Status::Complete {
87                        return Some(font);
88                    }
89                    best = Some(font);
90                }
91                None => continue,
92            }
93        }
94        let attrs = list.attributes;
95        // We don't have a complete mapping at this point, so time to check
96        // fallback fonts.
97        if cluster.info().is_emoji() {
98            if let Some(entry) = self.groups.emoji(&self.fonts, attrs) {
99                match entry.map_cluster(&mut self.fonts, cluster, synthesis, best.is_none()) {
100                    Some((font, status)) => {
101                        if status == Status::Complete {
102                            return Some(font);
103                        }
104                        best = Some(font);
105                    }
106                    None => {}
107                }
108            }
109        }
110        if !self.groups.state.fallbacks_ready {
111            self.groups.fill_fallbacks(&self.fonts);
112        }
113        for &family in &self.groups.state.fallbacks {
114            let entry = match self.groups.state.fallback_map.get_mut(&(family, attrs)) {
115                Some(entry) => entry,
116                _ => match self.fonts.query(family, attrs) {
117                    Some(font) => {
118                        self.groups
119                            .state
120                            .fallback_map
121                            .insert((family, attrs), font.selector(attrs).into());
122                        self.groups
123                            .state
124                            .fallback_map
125                            .get_mut(&(family, attrs))
126                            .unwrap()
127                    }
128                    _ => continue,
129                },
130            };
131            match entry.map_cluster(&mut self.fonts, cluster, synthesis, best.is_none()) {
132                Some((font, status)) => {
133                    if status == Status::Complete {
134                        return Some(font);
135                    }
136                    best = Some(font);
137                }
138                None => continue,
139            }
140        }
141        best
142    }
143}
144
145pub struct FontCache {
146    pub index: Arc<StaticIndex>,
147    sources: FxHashMap<SourceId, Option<(SharedData, Epoch)>>,
148    epoch: Epoch,
149}
150
151impl FontCache {
152    pub fn default() -> Self {
153        let index = StaticIndex::global().clone();
154        FontCache {
155            index,
156            sources: FxHashMap::default(),
157            epoch: 0,
158        }
159    }
160
161    /// Returns a font entry that matches the specified family and
162    /// attributes.
163    pub fn query<'a>(
164        &'a self,
165        family: impl Into<FamilyKey<'a>>,
166        attributes: impl Into<Attributes>,
167    ) -> Option<FontEntry<'a>> {
168        self.index.query(family, attributes)
169    }
170
171    #[cfg(feature = "emacs")]
172    pub fn list<'a>(&'a self, spec: FontSpec) -> Vec<FontEntry<'a>> {
173        self.index.list(spec)
174    }
175    #[cfg(feature = "emacs")]
176    pub fn match_<'a>(&'a self, spec: FontSpec) -> Option<FontEntry<'a>> {
177        self.index.match_(spec)
178    }
179
180    /// Returns a font entry for the specified identifier.
181    pub fn font_by_id<'a>(&'a self, id: FontId) -> Option<FontEntry<'a>> {
182        self.index.font_by_id(id)
183    }
184
185    /// Returns a font matching the specified key.
186    pub fn get<'k>(&mut self, key: impl Into<FontKey<'k>>) -> Option<Font> {
187        let (source_id, offset, attributes, key) = match key.into() {
188            FontKey::Id(id) => {
189                let font = self.font_by_id(id)?;
190                (
191                    font.source().id(),
192                    font.offset(),
193                    font.attributes(),
194                    font.cache_key(),
195                )
196            }
197            FontKey::Descriptor(family, attrs) => {
198                let font = self.query(family, attrs)?;
199                (
200                    font.source().id(),
201                    font.offset(),
202                    font.attributes(),
203                    font.cache_key(),
204                )
205            }
206        };
207        let epoch = self.epoch;
208        match self.sources.get_mut(&source_id) {
209            Some(data) => {
210                return data.as_mut().map(|d| {
211                    d.1 = epoch;
212                    Font {
213                        data: d.0.clone(),
214                        offset,
215                        attributes,
216                        key,
217                    }
218                })
219            }
220            _ => {}
221        }
222        let source = self.index.base.sources.get(source_id.to_usize())?;
223        match source.get() {
224            Some(data) => {
225                self.sources.insert(source_id, Some((data.clone(), epoch)));
226                Some(Font {
227                    data,
228                    offset,
229                    attributes,
230                    key,
231                })
232            }
233            _ => {
234                self.sources.insert(source_id, None);
235                None
236            }
237        }
238    }
239}
240
241/// Internal cache of font groups.
242///
243/// The strategy here uses a two layer caching system that maps user font
244/// groups to a list of resolved font identifiers and a group
245/// identifier. The group identifier is then mapped to a transient
246/// list of cached fonts. This structure provides reasonably fast lookup
247/// while also allowing group invalidation and eviction without the
248/// need for notifying user code or for a messy observer/listener style
249/// system. Essentially, this is more complex than desired, but the complexity
250/// is entirely encapsulated here.
251#[derive(Default)]
252struct GroupCache {
253    /// Maps from a user font descriptor key to a list of font
254    /// identifiers.
255    key_map: FxHashMap<FontGroupKey, CachedGroup>,
256    /// Temporary storage for parsing a user font descriptor.
257    tmp: Vec<(FontId, Attributes)>,
258    /// Next descriptor identifier.
259    next_id: u64,
260    /// Map from descriptor identifier to the list of cached fonts. This
261    /// is semi-transient: usually per layout session.
262    font_map: FxHashMap<FontGroupId, CachedFontList>,
263    /// Fonts referenced by the ranges in `font_map`.
264    fonts: Vec<CachedFont>,
265    /// Currently selected descriptor/script/language state for mapping
266    /// clusters.
267    state: GroupCacheState,
268}
269
270/// Current mapping state for a descriptor cache.
271struct GroupCacheState {
272    /// Selected identifier.
273    id: FontGroupId,
274    /// Selected font list.
275    fonts: CachedFontList,
276    /// Fallback state.
277    fallback: Option<(Script, Cjk)>,
278    /// True if the fallbacks list is current.
279    fallbacks_ready: bool,
280    /// Transient fallback cache to avoid excessive queries.
281    fallback_map: FxHashMap<(FamilyId, Attributes), CachedFont>,
282    /// Current list of fallback families.
283    fallbacks: Vec<FamilyId>,
284    /// True if we've attempted to load an emoji font.
285    emoji_ready: bool,
286    /// Cached emoji font.
287    emoji: Option<CachedFont>,
288}
289
290impl Default for GroupCacheState {
291    fn default() -> Self {
292        Self {
293            id: FontGroupId(!0),
294            fonts: CachedFontList::default(),
295            fallback: None,
296            fallbacks_ready: true,
297            fallback_map: FxHashMap::default(),
298            fallbacks: Vec::new(),
299            emoji_ready: false,
300            emoji: None,
301        }
302    }
303}
304
305impl GroupCacheState {
306    fn reset(&mut self) {
307        self.id = FontGroupId(!0);
308        self.fonts = CachedFontList::default();
309        self.fallback = None;
310        self.fallbacks_ready = true;
311        self.fallback_map.clear();
312        self.fallbacks.clear();
313        self.emoji_ready = false;
314        self.emoji = None;
315    }
316}
317
318impl GroupCache {
319    /// Returns a font group identifier for the specified families and attributes.
320    fn get(&mut self, fonts: &FontCache, names: &str, key: u64, attrs: Attributes) -> FontGroupId {
321        use std::collections::hash_map::Entry;
322        let key = (key, attrs);
323        // Fast path for a descriptor we've already seen.
324        match self.key_map.get_mut(&key) {
325            Some(item) => {
326                item.epoch = fonts.epoch;
327                match self.font_map.entry(item.id) {
328                    Entry::Occupied(..) => {}
329                    Entry::Vacant(e) => {
330                        let start = self.fonts.len();
331                        self.fonts.extend(
332                            item.data
333                                .get()
334                                .iter()
335                                .map(|&sel| (sel.0, sel.1, attrs).into()),
336                        );
337                        let end = self.fonts.len();
338                        e.insert(CachedFontList {
339                            attributes: attrs,
340                            start,
341                            end,
342                        });
343                    }
344                }
345                return item.id;
346            }
347            _ => {}
348        }
349        // Parse the descriptor and collect the font identifiers.
350        self.tmp.clear();
351        for family in parse_families(names) {
352            match fonts.query(family, attrs).map(|f| f.selector(attrs)) {
353                Some(sel) => self.tmp.push((sel.0, sel.1)),
354                _ => {}
355            }
356        }
357        // Slow path: linear search.
358        for (item_key, item) in &self.key_map {
359            if item_key.1 != attrs {
360                continue;
361            }
362            let existing = item.data.get();
363            if existing == &self.tmp {
364                match self.font_map.entry(item.id) {
365                    Entry::Occupied(..) => {}
366                    Entry::Vacant(e) => {
367                        let start = self.fonts.len();
368                        self.fonts.extend(
369                            item.data
370                                .get()
371                                .iter()
372                                .map(|&sel| (sel.0, sel.1, attrs).into()),
373                        );
374                        let end = self.fonts.len();
375                        e.insert(CachedFontList {
376                            attributes: attrs,
377                            start,
378                            end,
379                        });
380                    }
381                }
382                return item.id;
383            }
384        }
385        // Insert a new entry.
386        let id = FontGroupId(self.next_id);
387        self.next_id += 1;
388        let mut data = GroupData::Inline(0, [(FontId(0), Attributes::default()); MAX_INLINE]);
389        for font in &self.tmp {
390            data.push(font.0, font.1);
391        }
392        let start = self.fonts.len();
393        self.fonts
394            .extend(self.tmp.iter().map(|&sel| (sel.0, sel.1, attrs).into()));
395        let end = self.fonts.len();
396        self.font_map.insert(
397            id,
398            CachedFontList {
399                attributes: attrs,
400                start,
401                end,
402            },
403        );
404        let desc = CachedGroup {
405            id,
406            epoch: fonts.epoch,
407            data,
408        };
409        self.key_map.insert(key, desc);
410        id
411    }
412
413    /// Selects a descriptor for mapping clusters.
414    fn select(&mut self, id: FontGroupId) {
415        if self.state.id == id {
416            return;
417        }
418        match self.font_map.get(&id) {
419            Some(fonts) => self.state.fonts = *fonts,
420            _ => self.state.fonts = CachedFontList::default(),
421        }
422        self.state.id = id;
423    }
424
425    /// Selects a fallback state.
426    fn select_fallbacks(&mut self, script: Script, cjk: Cjk) {
427        if self.state.fallback != Some((script, cjk)) {
428            self.state.fallback = Some((script, cjk));
429            self.state.fallbacks_ready = false;
430            self.state.fallbacks.clear();
431        }
432    }
433
434    fn fill_fallbacks(&mut self, fonts: &FontCache) {
435        self.state.fallbacks.clear();
436        self.state.fallbacks_ready = true;
437        match self.state.fallback {
438            Some((script, cjk)) => {
439                self.state
440                    .fallbacks
441                    .extend_from_slice(fonts.index.fallbacks(script, cjk));
442            }
443            _ => {}
444        }
445    }
446
447    fn emoji(&mut self, fonts: &FontCache, attrs: Attributes) -> Option<&mut CachedFont> {
448        if !self.state.emoji_ready {
449            self.state.emoji_ready = true;
450            let family = fonts.index.emoji_family()?;
451            let sel = fonts.query(family, ())?.selector(attrs);
452            self.state.emoji = Some(sel.into());
453        }
454        self.state.emoji.as_mut()
455    }
456
457    /// Clears all transient state.
458    fn reset(&mut self) {
459        self.state.reset();
460        self.font_map.clear();
461        self.fonts.clear();
462    }
463
464    // fn prune(&mut self, epoch: Epoch, target_size: usize) {
465    //     if self.key_map.len() <= target_size {
466    //         return;
467    //     }
468    //     let mut count = self.key_map.len() - target_size;
469    //     self.key_map.retain(|_, v| {
470    //         if count != 0 && v.epoch < epoch {
471    //             count -= 1;
472    //             false
473    //         } else {
474    //             true
475    //         }
476    //     });
477    //     if count != 0 {
478    //         self.key_map.retain(|_, _| {
479    //             if count != 0 {
480    //                 count -= 1;
481    //                 false
482    //             } else {
483    //                 true
484    //             }
485    //         });
486    //     }
487    // }
488}
489
490struct CachedGroup {
491    id: FontGroupId,
492    epoch: Epoch,
493    data: GroupData,
494}
495
496#[derive(Clone)]
497enum GroupData {
498    Inline(u8, [(FontId, Attributes); MAX_INLINE]),
499    Heap(Vec<(FontId, Attributes)>),
500}
501
502impl GroupData {
503    // fn clear(&mut self) {
504    //     match self {
505    //         Self::Inline(len, _) => {
506    //             *len = 0;
507    //         }
508    //         Self::Heap(vec) => {
509    //             vec.clear();
510    //         }
511    //     }
512    // }
513
514    fn push(&mut self, font: FontId, attrs: Attributes) {
515        match self {
516            Self::Inline(len, ids) => {
517                if *len as usize == ids.len() {
518                    let mut vec = Vec::from(&ids[..]);
519                    vec.push((font, attrs));
520                    *self = Self::Heap(vec);
521                } else {
522                    ids[*len as usize] = (font, attrs);
523                    *len += 1;
524                }
525            }
526            Self::Heap(vec) => {
527                vec.push((font, attrs));
528            }
529        }
530    }
531
532    fn get(&self) -> &[(FontId, Attributes)] {
533        match self {
534            Self::Inline(len, ids) => &ids[..*len as usize],
535            Self::Heap(vec) => &vec,
536        }
537    }
538}
539
540#[derive(Copy, Clone, Default)]
541struct CachedFontList {
542    /// Attributes are necessary for fallback font selection.
543    attributes: Attributes,
544    /// Range of cached fonts.
545    start: usize,
546    /// ... ditto
547    end: usize,
548}
549
550struct CachedFont {
551    id: FontId,
552    font: Option<Font>,
553    charmap: CharmapProxy,
554    synth: Synthesis,
555    error: bool,
556}
557
558impl CachedFont {
559    #[inline]
560    fn map_cluster(
561        &mut self,
562        fonts: &mut FontCache,
563        cluster: &mut CharCluster,
564        synth: &mut Synthesis,
565        first: bool,
566    ) -> Option<(Font, Status)> {
567        if self.error {
568            return None;
569        }
570        let font = match &self.font {
571            Some(font) => font,
572            None => {
573                let font = fonts.get(self.id);
574                let font = match font {
575                    Some(f) => f,
576                    _ => {
577                        self.error = true;
578                        return None;
579                    }
580                };
581                self.charmap = CharmapProxy::from_font(&font.as_ref());
582                self.font = Some(font);
583                self.font.as_ref().unwrap()
584            }
585        };
586        let charmap = self.charmap.materialize(&font.as_ref());
587        let status = cluster.map(|ch| charmap.map(ch));
588        if status != Status::Discard || first {
589            *synth = self.synth;
590            Some((font.clone(), status))
591        } else {
592            None
593        }
594    }
595}
596
597impl From<(FontId, Attributes, Attributes)> for CachedFont {
598    fn from(v: (FontId, Attributes, Attributes)) -> Self {
599        let synth = v.1.synthesize(v.2);
600        Self {
601            id: v.0,
602            font: None,
603            charmap: CharmapProxy::default(),
604            synth,
605            error: false,
606        }
607    }
608}