Skip to main content

valo_text/
font.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use skrifa::metrics::Metrics;
5use skrifa::prelude::Size;
6use skrifa::MetadataProvider;
7
8/// `FontId` identifies a registered font within a [`FaceSet`] or [`FontCollection`].
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub struct FontId(
11    /// The zero-based registration index.
12    pub u32,
13);
14
15/// `FontData` is shared, immutable font-file storage supplied by the host.
16///
17/// It may wrap owned bytes or memory-mapped data and must remain readable for
18/// the lifetime of every [`Font`] created from it.
19pub type FontData = Arc<dyn AsRef<[u8]> + Send + Sync>;
20
21/// `FontAttrs` describes a face's position within a font family.
22#[derive(Clone, Copy, Debug, PartialEq)]
23pub struct FontAttrs {
24    /// `weight` is the CSS font weight, conventionally from 100 to 900.
25    pub weight: u16,
26    /// `italic` indicates whether the face uses an italic or oblique style.
27    pub italic: bool,
28    /// `stretch` is the CSS font-width percentage, where 100 is normal.
29    pub stretch: f32,
30}
31
32/// `NORMAL_STRETCH` is the CSS normal font-width percentage.
33pub const NORMAL_STRETCH: f32 = 100.0;
34
35impl Default for FontAttrs {
36    fn default() -> Self {
37        Self {
38            weight: 400,
39            italic: false,
40            stretch: NORMAL_STRETCH,
41        }
42    }
43}
44
45/// The instance-independent half of a parsed face: bytes, coverage, and
46/// the compiled shaping caches are IDENTICAL across a variable font's
47/// named instances, so every instance shares one of these (DirectWrite's
48/// model: instances enumerate separately, file state is shared).
49struct SharedFace {
50    data: FontData,
51    /// Which face inside `data` — TrueType collections (.ttc) pack
52    /// several; 0 for single-face files.
53    face_index: u32,
54    /// cmap materialized ONCE — fallback resolution is per-character and
55    /// must never re-parse the font (Skia caches per-typeface the same way).
56    charmap: HashMap<u32, u32>,
57    /// HarfBuzz's compiled shaping caches (GSUB/GDEF/cmap), built once —
58    /// the FontRef itself is cheap to reconstruct per shape (Skia's analog:
59    /// HB faces cached per typeface).
60    shaper_data: harfrust::ShaperData,
61}
62
63/// `FontUid` is the process-unique identity of one font instance.
64///
65/// Glyph caches may use it as a stable key because equal identifiers imply
66/// equal outlines and variation coordinates.
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
68pub struct FontUid(
69    /// The process-unique numeric identity.
70    pub u64,
71);
72
73impl FontUid {
74    fn next() -> FontUid {
75        use std::sync::atomic::{AtomicU64, Ordering};
76        static COUNTER: AtomicU64 = AtomicU64::new(1);
77        FontUid(COUNTER.fetch_add(1, Ordering::Relaxed))
78    }
79}
80
81/// `Font` is one parsed font face or named variable-font instance.
82///
83/// It retains immutable source bytes and exposes the names, attributes,
84/// coverage, and metrics needed for shaping and rendering.
85pub struct Font {
86    uid: FontUid,
87    shared: Arc<SharedFace>,
88    /// User-space variation coordinates when this font is a named instance
89    /// of a variable font ((axis tag, value) per axis); empty = the
90    /// file's default instance.
91    variation_coordinates: Vec<([u8; 4], f32)>,
92    /// The same coordinates in normalized variation space, for the skrifa
93    /// views (metrics, COLR paint graphs).
94    variation_location: skrifa::instance::Location,
95    /// HarfBuzz's compiled per-instance data (None = default instance).
96    shaper_instance: Option<harfrust::ShaperInstance>,
97    family: String,
98    /// Other names this face answers to: the file's localized family names
99    /// (fontdb keeps all of them) plus any host-registered alias (Skia's
100    /// `registerTypeface(typeface, familyName)`).
101    aliases: Vec<String>,
102    attrs: FontAttrs,
103    units_per_em: f32,
104    /// Font-unit metrics, y-up (ascent positive, descent negative).
105    ascent: f32,
106    descent: f32,
107    line_gap: f32,
108    /// Union of all glyph ink, font units y-up — Skia's fXMin/fXMax family.
109    bounds: Option<(f32, f32, f32, f32)>,
110    /// (offset from baseline, thickness) in font units, when the font says.
111    underline: Option<(f32, f32)>,
112    strikeout: Option<(f32, f32)>,
113}
114
115impl std::fmt::Debug for Font {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        f.debug_struct("Font")
118            .field("uid", &self.uid)
119            .field("family", &self.family)
120            .field("attrs", &self.attrs)
121            .finish_non_exhaustive()
122    }
123}
124
125impl Font {
126    fn parse(
127        family: &str,
128        attrs: FontAttrs,
129        data: FontData,
130        face_index: u32,
131        variation_coordinates: Vec<([u8; 4], f32)>,
132    ) -> Option<Self> {
133        let shared = SharedFace::parse(data, face_index)?;
134        Self::at_coordinates(shared, family, attrs, variation_coordinates)
135    }
136
137    /// One instance over already-parsed shared state — metrics and shaping
138    /// instance data are the only per-instance work.
139    fn at_coordinates(
140        shared: Arc<SharedFace>,
141        family: &str,
142        attrs: FontAttrs,
143        variation_coordinates: Vec<([u8; 4], f32)>,
144    ) -> Option<Self> {
145        let bytes: &[u8] = (*shared.data).as_ref();
146        let font = skrifa::FontRef::from_index(bytes, shared.face_index).ok()?;
147        let variation_location = font.axes().location(
148            variation_coordinates
149                .iter()
150                .map(|(tag, value)| (skrifa::Tag::new(tag), *value)),
151        );
152        let metrics = Metrics::new(&font, Size::unscaled(), &variation_location);
153        let shaper_instance = (!variation_coordinates.is_empty()).then(|| {
154            let harf = harfrust::FontRef::from_index(bytes, shared.face_index).ok();
155            harf.map(|harf| {
156                harfrust::ShaperInstance::from_variations(
157                    &harf,
158                    variation_coordinates
159                        .iter()
160                        .map(|(tag, value)| harfrust::Variation {
161                            tag: harfrust::Tag::new(tag),
162                            value: *value,
163                        }),
164                )
165            })
166        });
167        Some(Self {
168            uid: FontUid::next(),
169            family: family.to_owned(),
170            aliases: Vec::new(),
171            attrs,
172            units_per_em: metrics.units_per_em as f32,
173            ascent: metrics.ascent,
174            descent: metrics.descent,
175            line_gap: metrics.leading,
176            bounds: metrics.bounds.map(|b| (b.x_min, b.y_min, b.x_max, b.y_max)),
177            underline: metrics.underline.map(|d| (d.offset, d.thickness)),
178            strikeout: metrics.strikeout.map(|d| (d.offset, d.thickness)),
179            shared,
180            variation_coordinates,
181            variation_location,
182            shaper_instance: shaper_instance.flatten(),
183        })
184    }
185
186    /// `data` returns the source font-file bytes.
187    ///
188    /// For a font collection file, use [`Self::face_index`] to select this face.
189    pub fn data(&self) -> &[u8] {
190        (*self.shared.data).as_ref()
191    }
192
193    /// `face_index` returns this face's index within [`Self::data`].
194    pub fn face_index(&self) -> u32 {
195        self.shared.face_index
196    }
197
198    /// `variation_coordinates` returns user-space axis tags and values.
199    ///
200    /// It is empty for static fonts and default variable-font instances.
201    pub fn variation_coordinates(&self) -> &[([u8; 4], f32)] {
202        &self.variation_coordinates
203    }
204
205    pub(crate) fn variation_location(&self) -> &skrifa::instance::Location {
206        &self.variation_location
207    }
208
209    pub(crate) fn shaper_instance(&self) -> Option<&harfrust::ShaperInstance> {
210        self.shaper_instance.as_ref()
211    }
212
213    /// `uid` returns the process-unique identity of this font instance.
214    pub fn uid(&self) -> FontUid {
215        self.uid
216    }
217
218    /// `family` returns the font's primary family name.
219    pub fn family(&self) -> &str {
220        &self.family
221    }
222
223    /// `aliases` returns additional family names recognized for this font.
224    pub fn aliases(&self) -> &[String] {
225        &self.aliases
226    }
227
228    /// `matches` reports whether `name` matches the family or an alias.
229    ///
230    /// Matching is ASCII case-insensitive.
231    pub fn matches(&self, name: &str) -> bool {
232        self.family.eq_ignore_ascii_case(name)
233            || self.aliases.iter().any(|a| a.eq_ignore_ascii_case(name))
234    }
235
236    /// `add_alias` registers another family name if it is not already recognized.
237    pub fn add_alias(&mut self, name: &str) {
238        if !self.matches(name) {
239            self.aliases.push(name.to_owned());
240        }
241    }
242
243    /// `attrs` returns the face-selection attributes of this font.
244    pub fn attrs(&self) -> FontAttrs {
245        self.attrs
246    }
247
248    /// `ascent_px` returns the positive distance above the baseline at `size`.
249    pub fn ascent_px(&self, size: f32) -> f32 {
250        self.ascent * size / self.units_per_em
251    }
252
253    /// `descent_px` returns the positive distance below the baseline at `size`.
254    pub fn descent_px(&self, size: f32) -> f32 {
255        -self.descent * size / self.units_per_em
256    }
257
258    /// `line_height_px` returns the font's default line height at `size`.
259    pub fn line_height_px(&self, size: f32) -> f32 {
260        (self.ascent - self.descent + self.line_gap) * size / self.units_per_em
261    }
262
263    /// `units_per_em` returns the font's design-space units per em.
264    pub fn units_per_em(&self) -> f32 {
265        self.units_per_em
266    }
267
268    /// `ink_box_px` returns the font-wide ink bounds at `size`.
269    ///
270    /// The tuple is `(x_min, y_min, x_max, y_max)` in y-up coordinates around
271    /// the glyph origin. It returns `None` when the font provides no bounds.
272    pub fn ink_box_px(&self, size: f32) -> Option<(f32, f32, f32, f32)> {
273        let k = size / self.units_per_em;
274        self.bounds
275            .map(|(x0, y0, x1, y1)| (x0 * k, y0 * k, x1 * k, y1 * k))
276    }
277
278    /// `covers` reports whether the font maps a character to a glyph.
279    pub fn covers(&self, ch: char) -> bool {
280        self.shared.charmap.contains_key(&(ch as u32))
281    }
282
283    /// `glyph_for` returns the glyph identifier mapped from a character.
284    pub fn glyph_for(&self, ch: char) -> Option<u32> {
285        self.shared.charmap.get(&(ch as u32)).copied()
286    }
287
288    pub(crate) fn shaper_data(&self) -> &harfrust::ShaperData {
289        &self.shared.shaper_data
290    }
291
292    /// `underline_px` returns underline offset and thickness at `size`.
293    ///
294    /// The offset is positive below the baseline. Conventional values are used
295    /// when the font omits underline metrics.
296    pub fn underline_px(&self, size: f32) -> (f32, f32) {
297        self.decoration_px(self.underline, size, -0.1, 0.05)
298    }
299
300    /// `strikeout_px` returns strikeout offset and thickness at `size`.
301    ///
302    /// The offset is positive above the baseline. Conventional values are used
303    /// when the font omits strikeout metrics.
304    pub fn strikeout_px(&self, size: f32) -> (f32, f32) {
305        self.decoration_px(self.strikeout, size, 0.3, 0.05)
306    }
307
308    fn decoration_px(
309        &self,
310        metric: Option<(f32, f32)>,
311        size: f32,
312        default_offset: f32,
313        default_thickness: f32,
314    ) -> (f32, f32) {
315        match metric {
316            // Font units are y-up: positive offsets sit above the baseline.
317            Some((offset, thickness)) => (
318                offset * size / self.units_per_em,
319                (thickness * size / self.units_per_em).max(0.5),
320            ),
321            None => (size * default_offset, (size * default_thickness).max(0.5)),
322        }
323    }
324}
325
326/// `FontDemand` describes font requests that no registered face could satisfy.
327///
328/// Hosts can use it to load additional fonts and lay out affected text again.
329#[derive(Clone, Debug, Default, PartialEq)]
330pub struct FontDemand {
331    /// `families` lists requested family names with no registered match.
332    pub families: Vec<String>,
333    /// `codepoints` lists uncovered characters and their requested attributes.
334    pub codepoints: Vec<(char, FontAttrs)>,
335}
336
337impl FontDemand {
338    /// `is_empty` reports whether every font request was satisfied.
339    pub fn is_empty(&self) -> bool {
340        self.families.is_empty() && self.codepoints.is_empty()
341    }
342
343    pub(crate) fn add_family(&mut self, name: &str) {
344        if !self.families.iter().any(|f| f == name) {
345            self.families.push(name.to_owned());
346        }
347    }
348
349    pub(crate) fn add_codepoint(&mut self, ch: char, attrs: FontAttrs) {
350        if !self.codepoints.contains(&(ch, attrs)) {
351            self.codepoints.push((ch, attrs));
352        }
353    }
354}
355
356/// `FontSource` locates fonts that are not already registered.
357///
358/// A source may consult installed fonts, downloaded assets, or another
359/// host-owned repository. [`FontCollection`] consults sources in registration
360/// order.
361pub trait FontSource {
362    /// `family` returns every available face matching a family name.
363    fn family(&mut self, name: &str) -> Vec<Font>;
364
365    /// `face_for_codepoint` returns a covering face nearest to `attrs`.
366    fn face_for_codepoint(&mut self, codepoint: char, attrs: FontAttrs) -> Option<Font>;
367}
368
369/// `FaceSet` stores registered fonts and their global fallback order.
370///
371/// Cloning a face set shares parsed fonts, allowing callers to grow a snapshot
372/// without modifying existing holders.
373#[derive(Default, Clone)]
374pub struct FaceSet {
375    /// `Arc` per face: adding a font clones N pointers, never re-parses
376    /// (Skia registers typefaces incrementally).
377    fonts: Vec<Arc<Font>>,
378    fallbacks: Vec<FontId>,
379}
380
381impl FaceSet {
382    /// `new` creates an empty face set.
383    pub fn new() -> Self {
384        Self::default()
385    }
386
387    /// `register` adds font bytes under a family using default attributes.
388    ///
389    /// It returns `None` when face zero cannot be parsed.
390    pub fn register(&mut self, family: &str, bytes: Vec<u8>) -> Option<FontId> {
391        self.register_with(family, FontAttrs::default(), bytes)
392    }
393
394    /// `register_with` adds font bytes under a family with explicit attributes.
395    ///
396    /// It returns `None` when face zero cannot be parsed.
397    pub fn register_with(
398        &mut self,
399        family: &str,
400        attrs: FontAttrs,
401        bytes: Vec<u8>,
402    ) -> Option<FontId> {
403        let data = unwrapped(Arc::new(bytes))?;
404        let font = Font::parse(family, attrs, data, 0, Vec::new())?;
405        Some(self.add(font))
406    }
407
408    /// `add` registers an already parsed font under its embedded names and attributes.
409    pub fn add(&mut self, font: Font) -> FontId {
410        self.fonts.push(Arc::new(font));
411        FontId(self.fonts.len() as u32 - 1)
412    }
413
414    /// `with_font` returns a cloned face set containing one additional font.
415    ///
416    /// Existing fonts remain shared and are not reparsed.
417    pub fn with_font(&self, font: Font) -> (FaceSet, FontId) {
418        let mut next = self.clone();
419        let id = next.add(font);
420        (next, id)
421    }
422
423    /// `with_fallbacks` returns a clone with its global fallback order replaced.
424    ///
425    /// Every identifier must belong to this face set.
426    pub fn with_fallbacks(&self, fallbacks: Vec<FontId>) -> FaceSet {
427        let mut next = self.clone();
428        next.fallbacks = fallbacks;
429        next
430    }
431
432    /// `add_fallback` appends a registered font to the global fallback order.
433    ///
434    /// Requested families are searched before this chain. `id` must belong to
435    /// this face set.
436    pub fn add_fallback(&mut self, id: FontId) {
437        self.fallbacks.push(id);
438    }
439
440    /// `grown_by` returns a clone extended with answers from a font source.
441    ///
442    /// Requested families are also registered under the requested name.
443    /// Uncovered codepoints add matching faces to the fallback chain. It returns
444    /// `None` when the source supplies nothing new.
445    pub fn grown_by(&self, source: &mut dyn FontSource, demand: &FontDemand) -> Option<FaceSet> {
446        let mut next = self.clone();
447        let mut grew = false;
448        for name in &demand.families {
449            if next.family(name).is_some() {
450                // Answered already (a stale demand) — resolution matches
451                // it now; asking the source again would duplicate faces.
452                continue;
453            }
454            grew |= next.register_answers(source.family(name), name);
455        }
456        for &(codepoint, attrs) in &demand.codepoints {
457            grew |= next.register_fallback_answer(source, codepoint, attrs);
458        }
459        grew.then_some(next)
460    }
461
462    fn register_answers(&mut self, faces: Vec<Font>, requested_name: &str) -> bool {
463        let mut added = false;
464        for mut font in faces {
465            font.add_alias(requested_name);
466            self.add(font);
467            added = true;
468        }
469        added
470    }
471
472    fn register_fallback_answer(
473        &mut self,
474        source: &mut dyn FontSource,
475        codepoint: char,
476        attrs: FontAttrs,
477    ) -> bool {
478        if is_private_use(codepoint) {
479            // Icon fonts resolve by their REGISTERED family name; a
480            // generic source "covering" the Private Use Area paints some
481            // vendor's glyphs where tofu is the honest render.
482            return false;
483        }
484        if self.covers_anywhere(codepoint) {
485            // A family registered moments ago (or the host) already covers
486            // it — resolution will find that face without a new fallback.
487            return false;
488        }
489        let Some(font) = source.face_for_codepoint(codepoint, attrs) else {
490            return false;
491        };
492        let id = self.add(font);
493        self.add_fallback(id);
494        true
495    }
496
497    fn covers_anywhere(&self, codepoint: char) -> bool {
498        self.fonts.iter().any(|font| font.covers(codepoint))
499    }
500
501    /// `is_empty` reports whether no fonts are registered.
502    pub fn is_empty(&self) -> bool {
503        self.fonts.is_empty()
504    }
505
506    /// `len` returns the number of registered fonts.
507    pub fn len(&self) -> usize {
508        self.fonts.len()
509    }
510
511    /// `get_arc` returns a shared handle to a registered font.
512    ///
513    /// # Panics
514    ///
515    /// Panics if `id` does not belong to this face set.
516    pub fn get_arc(&self, id: FontId) -> Arc<Font> {
517        self.fonts[id.0 as usize].clone()
518    }
519
520    /// `get` returns a registered font by identifier.
521    ///
522    /// # Panics
523    ///
524    /// Panics if `id` does not belong to this face set.
525    pub fn get(&self, id: FontId) -> &Font {
526        &self.fonts[id.0 as usize]
527    }
528
529    /// `family` returns the first registered font matching a family name or alias.
530    pub fn family(&self, name: &str) -> Option<FontId> {
531        let at = self.fonts.iter().position(|f| f.matches(name))?;
532        Some(FontId(at as u32))
533    }
534
535    /// `faces` iterates over every font matching a family name or alias.
536    ///
537    /// Results follow registration order and include separate subset faces.
538    pub fn faces<'a>(&'a self, name: &'a str) -> impl Iterator<Item = FontId> + 'a {
539        self.variants(name).map(|(id, _)| id)
540    }
541
542    /// `family_variant` returns the family face nearest to requested attributes.
543    ///
544    /// Width is matched first, then italic style and weight. Registration order
545    /// breaks ties.
546    pub fn family_variant(&self, name: &str, attrs: FontAttrs) -> Option<FontId> {
547        self.nearest(self.variants(name), attrs)
548    }
549
550    /// `resolve` selects a font for a character and requested style.
551    ///
552    /// It searches requested families in order, then global fallbacks. If no
553    /// font covers the character, it returns a font suitable for rendering
554    /// `.notdef`.
555    ///
556    /// # Panics
557    ///
558    /// Panics when the face set is empty.
559    pub fn resolve(&self, families: &[String], attrs: FontAttrs, ch: char) -> FontId {
560        self.resolve_covered(families, attrs, ch).0
561    }
562
563    /// `resolve_covered` selects a font and reports whether it covers the character.
564    ///
565    /// A `false` flag means the returned font will render `.notdef`.
566    ///
567    /// # Panics
568    ///
569    /// Panics when the face set is empty.
570    pub fn resolve_covered(
571        &self,
572        families: &[String],
573        attrs: FontAttrs,
574        ch: char,
575    ) -> (FontId, bool) {
576        for name in families {
577            let covering = self.variants(name).filter(|(_, f)| f.covers(ch));
578            if let Some(id) = self.nearest(covering, attrs) {
579                return (id, true);
580            }
581        }
582        let covering_fallbacks = self
583            .fallbacks
584            .iter()
585            .map(|id| (*id, self.get(*id)))
586            .filter(|(_, f)| f.covers(ch));
587        if let Some(id) = self.nearest(covering_fallbacks, attrs) {
588            return (id, true);
589        }
590        (self.tofu_face(families, attrs), false)
591    }
592
593    /// Every face answering to `name`, with its id.
594    fn variants<'a>(&'a self, name: &'a str) -> impl Iterator<Item = (FontId, &'a Font)> {
595        self.fonts
596            .iter()
597            .enumerate()
598            .filter(move |(_, f)| f.matches(name))
599            .map(|(at, f)| (FontId(at as u32), f.as_ref()))
600    }
601
602    /// CSS-style nearest among `faces`: width first, then matching style,
603    /// then smallest weight distance, ties to the first registered. That is
604    /// the CSS font-matching precedence; the distances themselves stay plain
605    /// absolute differences rather than CSS's directional walk.
606    fn nearest<'a>(
607        &self,
608        faces: impl Iterator<Item = (FontId, &'a Font)>,
609        attrs: FontAttrs,
610    ) -> Option<FontId> {
611        faces
612            .min_by_key(|(_, f)| {
613                (
614                    stretch_distance(f.attrs.stretch, attrs.stretch),
615                    f.attrs.italic != attrs.italic,
616                    f.attrs.weight.abs_diff(attrs.weight),
617                )
618            })
619            .map(|(id, _)| id)
620    }
621
622    /// Nothing covers `ch`: the style's best variant, else the first
623    /// fallback, else the first face — the tofu renders in SOMETHING.
624    fn tofu_face(&self, families: &[String], attrs: FontAttrs) -> FontId {
625        self.tofu_face_opt(families, attrs).unwrap_or_else(|| {
626            panic!(
627                "FontCollection has no fonts registered — register() one before building paragraphs"
628            );
629        })
630    }
631
632    /// [`Self::tofu_face`] that reports an EMPTY collection instead of
633    /// panicking — the `build_with` path, where an empty start is valid
634    /// (the chain is asked first; a char no source can render is skipped
635    /// and reported through the demand).
636    pub(crate) fn tofu_face_opt(&self, families: &[String], attrs: FontAttrs) -> Option<FontId> {
637        families
638            .iter()
639            .find_map(|name| self.family_variant(name, attrs))
640            .or_else(|| self.fallbacks.first().copied())
641            .or_else(|| (!self.fonts.is_empty()).then_some(FontId(0)))
642    }
643
644    /// [`Self::resolve_covered`] that can also report "no face exists at
645    /// all" (empty collection on the `build_with` path).
646    pub(crate) fn resolve_covered_opt(
647        &self,
648        families: &[String],
649        attrs: FontAttrs,
650        ch: char,
651    ) -> Option<(FontId, bool)> {
652        if self.fonts.is_empty() {
653            return None;
654        }
655        Some(self.resolve_covered(families, attrs, ch))
656    }
657}
658
659impl Font {
660    /// `from_bytes` parses face zero from owned font-file bytes.
661    ///
662    /// Family names and attributes come from the font. Localized family names
663    /// become aliases. It returns `None` when the bytes cannot be parsed.
664    pub fn from_bytes(bytes: Vec<u8>) -> Option<Font> {
665        Self::from_data(Arc::new(bytes), 0)
666    }
667
668    /// `from_data` parses one face from shared font-file storage.
669    ///
670    /// Use face index zero for a single-face file. It returns `None` when the
671    /// index or font data is invalid.
672    pub fn from_data(data: FontData, face_index: u32) -> Option<Font> {
673        let data = unwrapped(data)?;
674        Self::instance(data, face_index, Vec::new())
675    }
676
677    /// `instances_from_data` parses the registrable instances of one face.
678    ///
679    /// Static fonts produce one item. Variable fonts with named instances
680    /// produce one [`Font`] per instance. Invalid data returns an empty vector.
681    pub fn instances_from_data(data: FontData, face_index: u32) -> Vec<Font> {
682        let Some(data) = unwrapped(data) else {
683            return Vec::new();
684        };
685        let instances = named_instance_coordinates((*data).as_ref(), face_index);
686        if instances.is_empty() {
687            return Self::instance(data, face_index, Vec::new())
688                .into_iter()
689                .collect();
690        }
691        // The expensive halves (cmap, shaping caches) parse ONCE; each
692        // instance adds only its metrics and axis data.
693        let Some(shared) = SharedFace::parse(data, face_index) else {
694            return Vec::new();
695        };
696        instances
697            .into_iter()
698            .filter_map(|coordinates| Self::shared_instance(shared.clone(), coordinates))
699            .collect()
700    }
701
702    /// One face at one set of coordinates, self-described from its tables
703    /// (attrs overridden by the coordinates' weight/italic axes).
704    fn instance(data: FontData, face_index: u32, coordinates: Vec<([u8; 4], f32)>) -> Option<Font> {
705        let shared = SharedFace::parse(data, face_index)?;
706        Self::shared_instance(shared, coordinates)
707    }
708
709    /// [`Self::instance`] over already-shared file state.
710    fn shared_instance(shared: Arc<SharedFace>, coordinates: Vec<([u8; 4], f32)>) -> Option<Font> {
711        let bytes: &[u8] = (*shared.data).as_ref();
712        let (family, aliases) = embedded_names(bytes, shared.face_index)?;
713        let attrs = instance_attrs(embedded_attrs(bytes, shared.face_index), &coordinates);
714        let mut font = Font::at_coordinates(shared, &family, attrs, coordinates)?;
715        for name in &aliases {
716            font.add_alias(name);
717        }
718        Some(font)
719    }
720}
721
722impl SharedFace {
723    fn parse(data: FontData, face_index: u32) -> Option<Arc<Self>> {
724        let bytes: &[u8] = (*data).as_ref();
725        let font = skrifa::FontRef::from_index(bytes, face_index).ok()?;
726        let charmap = font
727            .charmap()
728            .mappings()
729            .map(|(code, glyph)| (code, glyph.to_u32()))
730            .collect();
731        let harf = harfrust::FontRef::from_index(bytes, face_index).ok()?;
732        let shaper_data = harfrust::ShaperData::new(&harf);
733        Some(Arc::new(Self {
734            data,
735            face_index,
736            charmap,
737            shaper_data,
738        }))
739    }
740}
741
742/// The (tag, value) coordinate rows of every fvar named instance.
743fn named_instance_coordinates(bytes: &[u8], face_index: u32) -> Vec<Vec<([u8; 4], f32)>> {
744    let Ok(font) = skrifa::FontRef::from_index(bytes, face_index) else {
745        return Vec::new();
746    };
747    let axis_tags: Vec<[u8; 4]> = font
748        .axes()
749        .iter()
750        .map(|axis| axis.tag().to_be_bytes())
751        .collect();
752    font.named_instances()
753        .iter()
754        .map(|instance| {
755            axis_tags
756                .iter()
757                .copied()
758                .zip(instance.user_coords())
759                .collect()
760        })
761        .collect()
762}
763
764/// A named instance's place in its family: the weight/italic/width axes
765/// override what the file's default-instance OS/2 table says.
766fn instance_attrs(base: FontAttrs, coordinates: &[([u8; 4], f32)]) -> FontAttrs {
767    let mut attrs = base;
768    for (tag, value) in coordinates {
769        match tag {
770            b"wght" => attrs.weight = value.clamp(1.0, 1000.0) as u16,
771            b"ital" => attrs.italic = *value >= 0.5,
772            b"slnt" => attrs.italic = attrs.italic || *value < 0.0,
773            // `wdth` is already a percentage, which is what CSS asks for.
774            b"wdth" => attrs.stretch = value.clamp(1.0, 1000.0),
775            _ => {}
776        }
777    }
778    attrs
779}
780
781/// Ordered width distance for variant matching. Quantized to 1/16 of a
782/// percent so it can be an integer sort key without collapsing the named
783/// widths (they are 12.5 apart at the closest).
784fn stretch_distance(candidate: f32, wanted: f32) -> u32 {
785    ((candidate - wanted).abs() * 16.0) as u32
786}
787
788/// WOFF2 arrives brotli-wrapped; faces parse the unwrapped TrueType bytes
789/// (icon and web fonts ship compressed — the CoreText managers accept
790/// them, so registration here does too). Identity for everything else.
791#[cfg(feature = "woff2")]
792fn unwrapped(data: FontData) -> Option<FontData> {
793    let bytes: &[u8] = (*data).as_ref();
794    if !woff2_patched::decode::is_woff2(bytes) {
795        return Some(data);
796    }
797    let unpacked = woff2_patched::decode::convert_woff2_to_ttf(&mut &bytes[..]).ok()?;
798    Some(Arc::new(unpacked))
799}
800
801#[cfg(not(feature = "woff2"))]
802fn unwrapped(data: FontData) -> Option<FontData> {
803    Some(data)
804}
805
806/// name table: the primary is the en typographic family (then en family,
807/// then any-language); every other family/typographic-family string rides
808/// along as an alias.
809fn embedded_names(data: &[u8], face_index: u32) -> Option<(String, Vec<String>)> {
810    use swash::StringId;
811    let font = swash::FontRef::from_index(data, face_index as usize)?;
812    let strings = font.localized_strings();
813    let pick = |id: StringId| {
814        strings
815            .find_by_id(id, Some("en"))
816            .or_else(|| strings.find_by_id(id, None))
817            .map(|s| s.to_string())
818    };
819    let primary = pick(StringId::TypographicFamily).or_else(|| pick(StringId::Family))?;
820    let aliases = strings
821        .filter(|s| matches!(s.id(), StringId::Family | StringId::TypographicFamily))
822        .map(|s| s.to_string())
823        .filter(|name| *name != primary)
824        .collect();
825    Some((primary, aliases))
826}
827
828/// OS/2 weight + width + style flags via swash attributes.
829fn embedded_attrs(data: &[u8], face_index: u32) -> FontAttrs {
830    let Some(font) = swash::FontRef::from_index(data, face_index as usize) else {
831        return FontAttrs::default();
832    };
833    let attrs = font.attributes();
834    FontAttrs {
835        weight: attrs.weight().0,
836        italic: attrs.style() != swash::Style::Normal,
837        stretch: attrs.stretch().to_percentage(),
838    }
839}
840
841/// Unicode Private Use Areas — codepoints whose meaning belongs to a
842/// specific registered font, never to a generic fallback source.
843fn is_private_use(codepoint: char) -> bool {
844    matches!(
845        codepoint,
846        '\u{E000}'..='\u{F8FF}' | '\u{F0000}'..='\u{FFFFD}' | '\u{100000}'..='\u{10FFFD}'
847    )
848}
849
850/// `FontCollection` owns registered faces and sources for resolving missing fonts.
851///
852/// Paragraph building consults sources in order and registers their answers.
853/// Unanswered requests accumulate until [`Self::take_unanswered`] is called.
854#[derive(Default)]
855pub struct FontCollection {
856    faces: FaceSet,
857    /// Consulted in order, first answer wins (Skia's manager priority:
858    /// registered bytes and downloaders before the platform database).
859    sources: Vec<Box<dyn FontSource>>,
860    /// Misses no source answered, since the last drain.
861    unanswered: FontDemand,
862}
863
864impl FontCollection {
865    /// `new` creates an empty collection with no font sources.
866    pub fn new() -> FontCollection {
867        FontCollection::default()
868    }
869
870    /// `faces` returns the faces currently registered in the collection.
871    ///
872    /// A built paragraph clones this set and remains independent of later changes.
873    pub fn faces(&self) -> &FaceSet {
874        &self.faces
875    }
876
877    /// `register` adds font bytes under a family using default attributes.
878    ///
879    /// It returns `None` when face zero cannot be parsed.
880    pub fn register(&mut self, family: &str, bytes: Vec<u8>) -> Option<FontId> {
881        self.faces.register(family, bytes)
882    }
883
884    /// `add` registers an already parsed font.
885    pub fn add(&mut self, font: Font) -> FontId {
886        self.faces.add(font)
887    }
888
889    /// `add_fallback` appends a registered font to the global fallback order.
890    ///
891    /// `id` must belong to this collection.
892    pub fn add_fallback(&mut self, id: FontId) {
893        self.faces.add_fallback(id);
894    }
895
896    /// `get` returns a registered font by identifier.
897    ///
898    /// # Panics
899    ///
900    /// Panics if `id` does not belong to this collection.
901    pub fn get(&self, id: FontId) -> &Font {
902        self.faces.get(id)
903    }
904
905    /// `len` returns the number of registered fonts.
906    pub fn len(&self) -> usize {
907        self.faces.len()
908    }
909
910    /// `family` returns the first font matching a family name or alias.
911    pub fn family(&self, name: &str) -> Option<FontId> {
912        self.faces.family(name)
913    }
914
915    /// `add_source` appends a source consulted when registered fonts cannot satisfy a request.
916    pub fn add_source(&mut self, source: impl FontSource + 'static) {
917        self.sources.push(Box::new(source));
918    }
919
920    /// `add_boxed_source` appends an already boxed font source.
921    pub fn add_boxed_source(&mut self, source: Box<dyn FontSource>) {
922        self.sources.push(source);
923    }
924
925    /// `is_empty` reports whether no fonts are currently registered.
926    pub fn is_empty(&self) -> bool {
927        self.faces.is_empty()
928    }
929
930    /// `adopt_faces` replaces the registered faces with a prepared set.
931    ///
932    /// This supports hosts that answer [`FontDemand`] using [`FaceSet::grown_by`].
933    pub fn adopt_faces(&mut self, faces: FaceSet) {
934        self.faces = faces;
935    }
936
937    /// `take_unanswered` drains font requests that no source could satisfy.
938    ///
939    /// Hosts may load and [`Self::register`] matching fonts before rebuilding
940    /// affected paragraphs.
941    pub fn take_unanswered(&mut self) -> FontDemand {
942        std::mem::take(&mut self.unanswered)
943    }
944
945    /// Resolution with growth: look up, else ask the sources in order,
946    /// registering what they answer (Skia's `findTypefaces` walking its
947    /// managers). `false` = nobody had it; the miss is recorded.
948    pub(crate) fn require_family(&mut self, name: &str) -> bool {
949        if self.faces.family(name).is_some() {
950            return true;
951        }
952        for source in &mut self.sources {
953            let faces = source.family(name);
954            if self.faces.register_answers(faces, name) {
955                return true;
956            }
957        }
958        self.unanswered.add_family(name);
959        false
960    }
961
962    /// The per-codepoint half (Skia's `defaultFallback(unicode, ..)`).
963    pub(crate) fn require_codepoint(&mut self, codepoint: char, attrs: FontAttrs) -> bool {
964        if self.faces.covers_anywhere(codepoint) {
965            return true;
966        }
967        for index in 0..self.sources.len() {
968            let (head, tail) = self.sources.split_at_mut(index);
969            let _ = head;
970            let source = &mut tail[0];
971            if self
972                .faces
973                .register_fallback_answer(source.as_mut(), codepoint, attrs)
974            {
975                return true;
976            }
977        }
978        self.unanswered.add_codepoint(codepoint, attrs);
979        false
980    }
981}