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 /// The face's id in the process that renders its glyphs, which every
55 /// instance answers alike; None when glyphs render from `data`.
56 remote_id: Option<u64>,
57 /// cmap materialized ONCE — fallback resolution is per-character and
58 /// must never re-parse the font (Skia caches per-typeface the same way).
59 charmap: HashMap<u32, u32>,
60 /// HarfBuzz's compiled shaping caches (GSUB/GDEF/cmap), built once —
61 /// the FontRef itself is cheap to reconstruct per shape (Skia's analog:
62 /// HB faces cached per typeface).
63 shaper_data: harfrust::ShaperData,
64}
65
66/// `FontUid` is the process-unique identity of one font instance.
67///
68/// Glyph caches may use it as a stable key because equal identifiers imply
69/// equal outlines and variation coordinates.
70#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
71pub struct FontUid(
72 /// The process-unique numeric identity.
73 pub u64,
74);
75
76impl FontUid {
77 fn next() -> FontUid {
78 use std::sync::atomic::{AtomicU64, Ordering};
79 static COUNTER: AtomicU64 = AtomicU64::new(1);
80 FontUid(COUNTER.fetch_add(1, Ordering::Relaxed))
81 }
82}
83
84/// `Font` is one parsed font face or named variable-font instance.
85///
86/// It retains immutable source bytes and exposes the names, attributes,
87/// coverage, and metrics needed for shaping and rendering.
88///
89/// A clone shares the parsed file and keeps the identifier: it is the same
90/// face at the same instance, as Skia shares an `SkTypeface` by reference.
91#[derive(Clone)]
92pub struct Font {
93 uid: FontUid,
94 shared: Arc<SharedFace>,
95 /// User-space variation coordinates when this font is a named instance
96 /// of a variable font ((axis tag, value) per axis); empty = the
97 /// file's default instance.
98 variation_coordinates: Vec<([u8; 4], f32)>,
99 /// The same coordinates in normalized variation space, for the skrifa
100 /// views (metrics, COLR paint graphs).
101 variation_location: skrifa::instance::Location,
102 /// HarfBuzz's compiled per-instance data (None = default instance).
103 shaper_instance: Option<harfrust::ShaperInstance>,
104 family: String,
105 /// Other names this face answers to: the file's localized family names
106 /// (fontdb keeps all of them) plus any host-registered alias (Skia's
107 /// `registerTypeface(typeface, familyName)`).
108 aliases: Vec<String>,
109 attrs: FontAttrs,
110 units_per_em: f32,
111 /// Font-unit metrics, y-up (ascent positive, descent negative).
112 ascent: f32,
113 descent: f32,
114 line_gap: f32,
115 /// The height of capital letters above the baseline in font units, 0 when the font does not
116 /// say (Skia's `fCapHeight`).
117 cap_height: f32,
118 /// Union of all glyph ink, font units y-up — Skia's fXMin/fXMax family.
119 bounds: Option<(f32, f32, f32, f32)>,
120 /// (offset from baseline, thickness) in font units, when the font says.
121 underline: Option<(f32, f32)>,
122 strikeout: Option<(f32, f32)>,
123}
124
125impl std::fmt::Debug for Font {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 f.debug_struct("Font")
128 .field("uid", &self.uid)
129 .field("family", &self.family)
130 .field("attrs", &self.attrs)
131 .finish_non_exhaustive()
132 }
133}
134
135/// Two fonts are equal when they are the same face at the same instance, which is what
136/// their identifiers say; equal identifiers imply equal outlines and coordinates.
137impl PartialEq for Font {
138 fn eq(&self, other: &Font) -> bool {
139 self.uid == other.uid
140 }
141}
142
143impl Font {
144 fn parse(
145 family: &str,
146 attrs: FontAttrs,
147 data: FontData,
148 face_index: u32,
149 variation_coordinates: Vec<([u8; 4], f32)>,
150 ) -> Option<Self> {
151 let shared = SharedFace::parse(data, face_index, None)?;
152 Self::at_coordinates(shared, family, attrs, variation_coordinates)
153 }
154
155 /// One instance over already-parsed shared state — metrics and shaping
156 /// instance data are the only per-instance work.
157 fn at_coordinates(
158 shared: Arc<SharedFace>,
159 family: &str,
160 attrs: FontAttrs,
161 variation_coordinates: Vec<([u8; 4], f32)>,
162 ) -> Option<Self> {
163 let bytes: &[u8] = (*shared.data).as_ref();
164 let font = skrifa::FontRef::from_index(bytes, shared.face_index).ok()?;
165 let variation_location = font.axes().location(
166 variation_coordinates
167 .iter()
168 .map(|(tag, value)| (skrifa::Tag::new(tag), *value)),
169 );
170 let metrics = Metrics::new(&font, Size::unscaled(), &variation_location);
171 let shaper_instance = (!variation_coordinates.is_empty()).then(|| {
172 let harf = harfrust::FontRef::from_index(bytes, shared.face_index).ok();
173 harf.map(|harf| {
174 harfrust::ShaperInstance::from_variations(
175 &harf,
176 variation_coordinates
177 .iter()
178 .map(|(tag, value)| harfrust::Variation {
179 tag: harfrust::Tag::new(tag),
180 value: *value,
181 }),
182 )
183 })
184 });
185 Some(Self {
186 uid: FontUid::next(),
187 family: family.to_owned(),
188 aliases: Vec::new(),
189 attrs,
190 units_per_em: metrics.units_per_em as f32,
191 ascent: metrics.ascent,
192 descent: metrics.descent,
193 line_gap: metrics.leading,
194 cap_height: metrics.cap_height.unwrap_or(0.0),
195 bounds: metrics.bounds.map(|b| (b.x_min, b.y_min, b.x_max, b.y_max)),
196 underline: metrics.underline.map(|d| (d.offset, d.thickness)),
197 strikeout: metrics.strikeout.map(|d| (d.offset, d.thickness)),
198 shared,
199 variation_coordinates,
200 variation_location,
201 shaper_instance: shaper_instance.flatten(),
202 })
203 }
204
205 /// `data` returns the source font-file bytes.
206 ///
207 /// For a font collection file, use [`Self::face_index`] to select this face.
208 pub fn data(&self) -> &[u8] {
209 (*self.shared.data).as_ref()
210 }
211
212 /// `face_index` returns this face's index within [`Self::data`].
213 pub fn face_index(&self) -> u32 {
214 self.shared.face_index
215 }
216
217 /// `remote_id` returns this face's id in the process that renders its glyphs.
218 ///
219 /// It is Skia's `SkTypefaceProxy::remoteTypefaceID`. A host whose glyphs are
220 /// rendered by another process makes its fonts with
221 /// [`Self::from_data_with_remote_id`] or [`Self::instances_from_data_with_remote_id`],
222 /// and its [`GlyphRaster`](crate::GlyphRaster) reads the id back to name the face to
223 /// that process, with [`Self::variation_coordinates`] naming the instance: every
224 /// named instance of a face answers the face's id. It is `None` for a font rendered
225 /// from its own bytes, and valo itself never reads it.
226 pub fn remote_id(&self) -> Option<u64> {
227 self.shared.remote_id
228 }
229
230 /// `variation_coordinates` returns user-space axis tags and values.
231 ///
232 /// It is empty for static fonts and default variable-font instances.
233 pub fn variation_coordinates(&self) -> &[([u8; 4], f32)] {
234 &self.variation_coordinates
235 }
236
237 pub(crate) fn variation_location(&self) -> &skrifa::instance::Location {
238 &self.variation_location
239 }
240
241 pub(crate) fn shaper_instance(&self) -> Option<&harfrust::ShaperInstance> {
242 self.shaper_instance.as_ref()
243 }
244
245 /// `uid` returns the process-unique identity of this font instance.
246 pub fn uid(&self) -> FontUid {
247 self.uid
248 }
249
250 /// `family` returns the font's primary family name.
251 pub fn family(&self) -> &str {
252 &self.family
253 }
254
255 /// `aliases` returns additional family names recognized for this font.
256 pub fn aliases(&self) -> &[String] {
257 &self.aliases
258 }
259
260 /// `matches` reports whether `name` matches the family or an alias.
261 ///
262 /// Matching is ASCII case-insensitive.
263 pub fn matches(&self, name: &str) -> bool {
264 self.family.eq_ignore_ascii_case(name)
265 || self.aliases.iter().any(|a| a.eq_ignore_ascii_case(name))
266 }
267
268 /// `add_alias` registers another family name if it is not already recognized.
269 pub fn add_alias(&mut self, name: &str) {
270 if !self.matches(name) {
271 self.aliases.push(name.to_owned());
272 }
273 }
274
275 /// `attrs` returns the face-selection attributes of this font.
276 pub fn attrs(&self) -> FontAttrs {
277 self.attrs
278 }
279
280 /// `ascent_px` returns the positive distance above the baseline at `size`.
281 pub fn ascent_px(&self, size: f32) -> f32 {
282 self.ascent * size / self.units_per_em
283 }
284
285 /// `cap_height_px` returns the height of capital letters above the baseline at `size`, 0
286 /// when the font does not say.
287 pub fn cap_height_px(&self, size: f32) -> f32 {
288 self.cap_height * size / self.units_per_em
289 }
290
291 /// `descent_px` returns the positive distance below the baseline at `size`.
292 pub fn descent_px(&self, size: f32) -> f32 {
293 -self.descent * size / self.units_per_em
294 }
295
296 /// `line_height_px` returns the font's default line height at `size`.
297 pub fn line_height_px(&self, size: f32) -> f32 {
298 (self.ascent - self.descent + self.line_gap) * size / self.units_per_em
299 }
300
301 /// `units_per_em` returns the font's design-space units per em.
302 pub fn units_per_em(&self) -> f32 {
303 self.units_per_em
304 }
305
306 /// `ink_box_px` returns the font-wide ink bounds at `size`.
307 ///
308 /// The tuple is `(x_min, y_min, x_max, y_max)` in y-up coordinates around
309 /// the glyph origin. It returns `None` when the font provides no bounds.
310 pub fn ink_box_px(&self, size: f32) -> Option<(f32, f32, f32, f32)> {
311 let k = size / self.units_per_em;
312 self.bounds
313 .map(|(x0, y0, x1, y1)| (x0 * k, y0 * k, x1 * k, y1 * k))
314 }
315
316 /// `covers` reports whether the font maps a character to a glyph.
317 pub fn covers(&self, ch: char) -> bool {
318 self.shared.charmap.contains_key(&(ch as u32))
319 }
320
321 /// `glyph_for` returns the glyph identifier mapped from a character.
322 pub fn glyph_for(&self, ch: char) -> Option<u32> {
323 self.shared.charmap.get(&(ch as u32)).copied()
324 }
325
326 pub(crate) fn shaper_data(&self) -> &harfrust::ShaperData {
327 &self.shared.shaper_data
328 }
329
330 /// `underline_px` returns underline offset and thickness at `size`.
331 ///
332 /// The offset is positive below the baseline. Conventional values are used
333 /// when the font omits underline metrics.
334 pub fn underline_px(&self, size: f32) -> (f32, f32) {
335 self.decoration_px(self.underline, size, -0.1, 0.05)
336 }
337
338 /// `strikeout_px` returns strikeout offset and thickness at `size`.
339 ///
340 /// The offset is positive above the baseline. Conventional values are used
341 /// when the font omits strikeout metrics.
342 pub fn strikeout_px(&self, size: f32) -> (f32, f32) {
343 self.decoration_px(self.strikeout, size, 0.3, 0.05)
344 }
345
346 fn decoration_px(
347 &self,
348 metric: Option<(f32, f32)>,
349 size: f32,
350 default_offset: f32,
351 default_thickness: f32,
352 ) -> (f32, f32) {
353 match metric {
354 // Font units are y-up: positive offsets sit above the baseline.
355 Some((offset, thickness)) => (
356 offset * size / self.units_per_em,
357 (thickness * size / self.units_per_em).max(0.5),
358 ),
359 None => (size * default_offset, (size * default_thickness).max(0.5)),
360 }
361 }
362}
363
364/// `FontDemand` describes font requests that no registered face could satisfy.
365///
366/// Hosts can use it to load additional fonts and lay out affected text again.
367#[derive(Clone, Debug, Default, PartialEq)]
368pub struct FontDemand {
369 /// `families` lists requested family names with no registered match.
370 pub families: Vec<String>,
371 /// `codepoints` lists uncovered characters and their requested attributes.
372 pub codepoints: Vec<(char, FontAttrs)>,
373}
374
375impl FontDemand {
376 /// `is_empty` reports whether every font request was satisfied.
377 pub fn is_empty(&self) -> bool {
378 self.families.is_empty() && self.codepoints.is_empty()
379 }
380
381 /// `add_family` records a family name no face answered, once.
382 pub fn add_family(&mut self, name: &str) {
383 if !self.families.iter().any(|f| f == name) {
384 self.families.push(name.to_owned());
385 }
386 }
387
388 /// `add_codepoint` records a character no face covered at `attrs`, once.
389 pub fn add_codepoint(&mut self, ch: char, attrs: FontAttrs) {
390 if !self.codepoints.contains(&(ch, attrs)) {
391 self.codepoints.push((ch, attrs));
392 }
393 }
394}
395
396/// `FontSource` locates fonts that are not already registered.
397///
398/// A source may consult installed fonts, downloaded assets, or another
399/// host-owned repository. [`FontCollection`] consults sources in registration
400/// order.
401pub trait FontSource {
402 /// `family` returns every available face matching a family name.
403 fn family(&mut self, name: &str) -> Vec<Font>;
404
405 /// `face_for_codepoint` returns a covering face nearest to `attrs`.
406 fn face_for_codepoint(&mut self, codepoint: char, attrs: FontAttrs) -> Option<Font>;
407}
408
409/// `match_style` returns the font nearest `attrs` among `fonts`, by CSS font
410/// matching's precedence: width, then style, then weight; ties to the first.
411///
412/// It is Skia's `SkFontStyleSet::matchStyle`, for a [`FontSource`] choosing
413/// among a family's faces, or among a variable face's named instances, so that
414/// a bold span's fallback arrives bold. The distances are plain absolute
415/// differences rather than CSS's directional walk: a request between two
416/// weights takes the closer, not the one CSS's direction prefers. It returns
417/// `None` when `fonts` is empty.
418pub fn match_style<'a>(
419 fonts: impl IntoIterator<Item = &'a Font>,
420 attrs: FontAttrs,
421) -> Option<&'a Font> {
422 fonts
423 .into_iter()
424 .min_by_key(|font| style_distance(font.attrs, attrs))
425}
426
427/// `FaceSet` stores registered fonts and their global fallback order.
428///
429/// Cloning a face set shares parsed fonts, allowing callers to grow a snapshot
430/// without modifying existing holders.
431#[derive(Default, Clone)]
432pub struct FaceSet {
433 /// `Arc` per face: adding a font clones N pointers, never re-parses
434 /// (Skia registers typefaces incrementally).
435 fonts: Vec<Arc<Font>>,
436 fallbacks: Vec<FontId>,
437 /// The faces answering to each name, keyed as [`name_key`] spells it, in
438 /// registration order. Resolution asks per character, so a name is a
439 /// lookup rather than a walk over every face comparing strings.
440 by_name: HashMap<String, Vec<FontId>>,
441}
442
443/// `StyleFaces` is one text style's font resolution, prepared by
444/// [`FaceSet::style_faces`].
445///
446/// It holds face identifiers only, ordered for [`FaceSet::resolve_prepared`];
447/// it is stale once a face is added to the set it came from.
448#[derive(Clone, Debug)]
449pub struct StyleFaces {
450 /// The requested families' faces, in request order, nearest-first.
451 families: Vec<Vec<FontId>>,
452 /// The fallback chain, nearest-first.
453 fallbacks: Vec<FontId>,
454 /// Where tofu renders when nothing covers: the style's best variant,
455 /// else the first fallback, else the first face; `None` on an empty set.
456 tofu: Option<FontId>,
457}
458
459impl FaceSet {
460 /// `new` creates an empty face set.
461 pub fn new() -> Self {
462 Self::default()
463 }
464
465 /// `register` adds font bytes under a family, with the weight, slant and width the file
466 /// declares.
467 ///
468 /// The face answers only to `family`, not to the names inside the file, as Flutter
469 /// registers a font under the family a host gives it, so the four static faces of one
470 /// family registered under its name select by weight like one family. It returns `None`
471 /// when face zero cannot be parsed.
472 pub fn register(&mut self, family: &str, bytes: Vec<u8>) -> Option<FontId> {
473 let (data, face_index) = unwrapped(Arc::new(bytes), 0)?;
474 let attrs = embedded_attrs((*data).as_ref(), face_index);
475 let font = Font::parse(family, attrs, data, face_index, Vec::new())?;
476 Some(self.add(font))
477 }
478
479 /// `register_with` adds font bytes under a family with explicit attributes.
480 ///
481 /// It returns `None` when face zero cannot be parsed.
482 pub fn register_with(
483 &mut self,
484 family: &str,
485 attrs: FontAttrs,
486 bytes: Vec<u8>,
487 ) -> Option<FontId> {
488 let (data, face_index) = unwrapped(Arc::new(bytes), 0)?;
489 let font = Font::parse(family, attrs, data, face_index, Vec::new())?;
490 Some(self.add(font))
491 }
492
493 /// `add` registers an already parsed font under its embedded names and attributes.
494 pub fn add(&mut self, font: Font) -> FontId {
495 let id = FontId(self.fonts.len() as u32);
496 let aliases = font.aliases().iter().map(String::as_str);
497 for name in std::iter::once(font.family()).chain(aliases) {
498 let ids = self.by_name.entry(name_key(name)).or_default();
499 if ids.last() != Some(&id) {
500 ids.push(id);
501 }
502 }
503 self.fonts.push(Arc::new(font));
504 id
505 }
506
507 /// `with_font` returns a cloned face set containing one additional font.
508 ///
509 /// Existing fonts remain shared and are not reparsed.
510 pub fn with_font(&self, font: Font) -> (FaceSet, FontId) {
511 let mut next = self.clone();
512 let id = next.add(font);
513 (next, id)
514 }
515
516 /// `with_fallbacks` returns a clone with its global fallback order replaced.
517 ///
518 /// Every identifier must belong to this face set.
519 pub fn with_fallbacks(&self, fallbacks: Vec<FontId>) -> FaceSet {
520 let mut next = self.clone();
521 next.fallbacks = fallbacks;
522 next
523 }
524
525 /// `add_fallback` appends a registered font to the global fallback order.
526 ///
527 /// Requested families are searched before this chain. `id` must belong to
528 /// this face set.
529 pub fn add_fallback(&mut self, id: FontId) {
530 self.fallbacks.push(id);
531 }
532
533 /// `grown_by` returns a clone extended with answers from a font source.
534 ///
535 /// Requested families are also registered under the requested name.
536 /// Uncovered codepoints add matching faces to the fallback chain. It returns
537 /// `None` when the source supplies nothing new.
538 pub fn grown_by(&self, source: &mut dyn FontSource, demand: &FontDemand) -> Option<FaceSet> {
539 let mut next = self.clone();
540 let mut grew = false;
541 for name in &demand.families {
542 if next.family(name).is_some() {
543 // Answered already (a stale demand) — resolution matches
544 // it now; asking the source again would duplicate faces.
545 continue;
546 }
547 grew |= next.register_answers(source.family(name), name);
548 }
549 for &(codepoint, attrs) in &demand.codepoints {
550 grew |= next.register_fallback_answer(source, codepoint, attrs);
551 }
552 grew.then_some(next)
553 }
554
555 fn register_answers(&mut self, faces: Vec<Font>, requested_name: &str) -> bool {
556 let mut added = false;
557 for mut font in faces {
558 font.add_alias(requested_name);
559 self.add(font);
560 added = true;
561 }
562 added
563 }
564
565 fn register_fallback_answer(
566 &mut self,
567 source: &mut dyn FontSource,
568 codepoint: char,
569 attrs: FontAttrs,
570 ) -> bool {
571 if is_private_use(codepoint) {
572 // Icon fonts resolve by their REGISTERED family name; a
573 // generic source "covering" the Private Use Area paints some
574 // vendor's glyphs where tofu is the honest render.
575 return false;
576 }
577 if self.fallbacks_cover(codepoint) {
578 // Already in the chain — resolution will find that face without
579 // a second copy of it.
580 return false;
581 }
582 let Some(font) = source.face_for_codepoint(codepoint, attrs) else {
583 return false;
584 };
585 let id = self.add(font);
586 self.add_fallback(id);
587 true
588 }
589
590 /// Whether the global fallback chain covers `codepoint`.
591 ///
592 /// A fallback answer is only ever reached through this chain, so this —
593 /// not "some registered face" — decides whether another one is needed.
594 fn fallbacks_cover(&self, codepoint: char) -> bool {
595 self.fallbacks
596 .iter()
597 .any(|id| self.get(*id).covers(codepoint))
598 }
599
600 /// Whether resolution finds a face covering `ch`.
601 ///
602 /// This is the walk [`Self::resolve_covered`] makes, so a face resolution
603 /// cannot reach — registered under a name nobody asked for and absent from
604 /// the fallback chain, as an icon font is — does not pass for coverage it
605 /// will never supply. Asking "does some registered face cover it" instead
606 /// lets such a font suppress the fallback the text actually needs.
607 pub(crate) fn covered_by_resolution(
608 &self,
609 families: &[String],
610 attrs: FontAttrs,
611 ch: char,
612 ) -> bool {
613 matches!(
614 self.resolve_covered_opt(families, attrs, ch),
615 Some((_, true))
616 )
617 }
618
619 /// `is_empty` reports whether no fonts are registered.
620 pub fn is_empty(&self) -> bool {
621 self.fonts.is_empty()
622 }
623
624 /// `len` returns the number of registered fonts.
625 pub fn len(&self) -> usize {
626 self.fonts.len()
627 }
628
629 /// `get_arc` returns a shared handle to a registered font.
630 ///
631 /// # Panics
632 ///
633 /// Panics if `id` does not belong to this face set.
634 pub fn get_arc(&self, id: FontId) -> Arc<Font> {
635 self.fonts[id.0 as usize].clone()
636 }
637
638 /// `get` returns a registered font by identifier.
639 ///
640 /// # Panics
641 ///
642 /// Panics if `id` does not belong to this face set.
643 pub fn get(&self, id: FontId) -> &Font {
644 &self.fonts[id.0 as usize]
645 }
646
647 /// `family` returns the first registered font matching a family name or alias.
648 pub fn family(&self, name: &str) -> Option<FontId> {
649 self.by_name.get(&name_key(name))?.first().copied()
650 }
651
652 /// `faces` iterates over every font matching a family name or alias.
653 ///
654 /// Results follow registration order and include separate subset faces.
655 pub fn faces<'a>(&'a self, name: &'a str) -> impl Iterator<Item = FontId> + 'a {
656 self.variants(name).map(|(id, _)| id)
657 }
658
659 /// `family_variant` returns the family face nearest to requested attributes.
660 ///
661 /// Faces are matched as [`match_style`] matches them: width first, then
662 /// italic style and weight. Registration order breaks ties.
663 pub fn family_variant(&self, name: &str, attrs: FontAttrs) -> Option<FontId> {
664 let nearest = match_style(self.variants(name).map(|(_, font)| font), attrs)?;
665 self.faces(name).find(|id| self.get(*id) == nearest)
666 }
667
668 /// `resolve` selects a font for a character and requested style.
669 ///
670 /// It searches requested families in order, then global fallbacks. If no
671 /// font covers the character, it returns a font suitable for rendering
672 /// `.notdef`.
673 ///
674 /// # Panics
675 ///
676 /// Panics when the face set is empty.
677 pub fn resolve(&self, families: &[String], attrs: FontAttrs, ch: char) -> FontId {
678 self.resolve_covered(families, attrs, ch).0
679 }
680
681 /// `resolve_covered` selects a font and reports whether it covers the character.
682 ///
683 /// A `false` flag means the returned font will render `.notdef`.
684 ///
685 /// # Panics
686 ///
687 /// Panics when the face set is empty.
688 pub fn resolve_covered(
689 &self,
690 families: &[String],
691 attrs: FontAttrs,
692 ch: char,
693 ) -> (FontId, bool) {
694 let faces = self.style_faces(families, attrs);
695 self.resolve_prepared(&faces, ch).unwrap_or_else(|| {
696 panic!(
697 "FontCollection has no fonts registered — register() one before building paragraphs"
698 );
699 })
700 }
701
702 /// `style_faces` prepares one style's resolution: the faces of each
703 /// requested family and of the fallback chain, nearest-first to `attrs`.
704 ///
705 /// A run of text asks for a font per character; prepared once per style,
706 /// each character is a coverage walk over these lists and no name lookup
707 /// (Skia's `SkFontCollection::findTypefaces`, done once per text style).
708 pub fn style_faces(&self, families: &[String], attrs: FontAttrs) -> StyleFaces {
709 let fallbacks = self.fallbacks.iter().map(|id| (*id, self.get(*id)));
710 StyleFaces {
711 families: families
712 .iter()
713 .map(|name| self.nearest_first(self.variants(name), attrs))
714 .collect(),
715 fallbacks: self.nearest_first(fallbacks, attrs),
716 tofu: self.tofu_face_opt(families, attrs),
717 }
718 }
719
720 /// `resolve_prepared` selects a font for `ch` from a prepared style.
721 ///
722 /// The first covering face of the first requested family that has one
723 /// wins, then the fallback chain's; the flag is `false` when nothing
724 /// covers and the returned font renders `.notdef`. `None` when the face
725 /// set is empty.
726 pub fn resolve_prepared(&self, faces: &StyleFaces, ch: char) -> Option<(FontId, bool)> {
727 let covering = faces
728 .families
729 .iter()
730 .chain(std::iter::once(&faces.fallbacks))
731 .flatten()
732 .find(|id| self.get(**id).covers(ch));
733 match covering {
734 Some(id) => Some((*id, true)),
735 None => faces.tofu.map(|id| (id, false)),
736 }
737 }
738
739 /// Every face answering to `name`, with its id, in registration order.
740 fn variants<'a>(&'a self, name: &str) -> impl Iterator<Item = (FontId, &'a Font)> + 'a {
741 self.by_name
742 .get(&name_key(name))
743 .into_iter()
744 .flatten()
745 .map(|id| (*id, self.get(*id)))
746 }
747
748 /// `faces` in [`match_style`]'s order: the face it would pick first, and
749 /// so on, with ties in registration order.
750 fn nearest_first<'a>(
751 &self,
752 faces: impl Iterator<Item = (FontId, &'a Font)>,
753 attrs: FontAttrs,
754 ) -> Vec<FontId> {
755 let mut faces: Vec<(FontId, &Font)> = faces.collect();
756 faces.sort_by_key(|(_, f)| style_distance(f.attrs, attrs));
757 faces.into_iter().map(|(id, _)| id).collect()
758 }
759
760 /// [`Self::tofu_face`] that reports an EMPTY collection instead of
761 /// panicking — the `build_with` path, where an empty start is valid
762 /// (the chain is asked first; a char no source can render is skipped
763 /// and reported through the demand).
764 pub(crate) fn tofu_face_opt(&self, families: &[String], attrs: FontAttrs) -> Option<FontId> {
765 families
766 .iter()
767 .find_map(|name| self.family_variant(name, attrs))
768 .or_else(|| self.fallbacks.first().copied())
769 .or_else(|| (!self.fonts.is_empty()).then_some(FontId(0)))
770 }
771
772 /// [`Self::resolve_covered`] that can also report "no face exists at
773 /// all" (empty collection on the `build_with` path).
774 pub(crate) fn resolve_covered_opt(
775 &self,
776 families: &[String],
777 attrs: FontAttrs,
778 ch: char,
779 ) -> Option<(FontId, bool)> {
780 if self.fonts.is_empty() {
781 return None;
782 }
783 Some(self.resolve_covered(families, attrs, ch))
784 }
785}
786
787/// The spelling a family name or alias is indexed under: names match
788/// ASCII-case-insensitively (as [`Font::matches`] compares them).
789fn name_key(name: &str) -> String {
790 name.to_ascii_lowercase()
791}
792
793impl Font {
794 /// `from_bytes` parses face zero from owned font-file bytes.
795 ///
796 /// Family names and attributes come from the font. Localized family names
797 /// become aliases. It returns `None` when the bytes cannot be parsed.
798 pub fn from_bytes(bytes: Vec<u8>) -> Option<Font> {
799 Self::from_data(Arc::new(bytes), 0)
800 }
801
802 /// `from_data` parses one face from shared font-file storage.
803 ///
804 /// Use face index zero for a single-face file. It returns `None` when the
805 /// index or font data is invalid.
806 pub fn from_data(data: FontData, face_index: u32) -> Option<Font> {
807 Self::parse_face(data, face_index, None)
808 }
809
810 /// `from_data_with_remote_id` parses one face whose glyphs another process renders.
811 ///
812 /// It is [`Self::from_data`] for a font that names its face to that process by
813 /// `remote_id`, which [`Self::remote_id`] answers.
814 pub fn from_data_with_remote_id(
815 data: FontData,
816 face_index: u32,
817 remote_id: u64,
818 ) -> Option<Font> {
819 Self::parse_face(data, face_index, Some(remote_id))
820 }
821
822 /// `instances_from_data` parses the registrable instances of one face.
823 ///
824 /// Static fonts produce one item. Variable fonts with named instances
825 /// produce one [`Font`] per instance. Invalid data returns an empty vector.
826 pub fn instances_from_data(data: FontData, face_index: u32) -> Vec<Font> {
827 Self::parse_instances(data, face_index, None)
828 }
829
830 /// `instances_from_data_with_remote_id` parses the registrable instances of one face
831 /// whose glyphs another process renders.
832 ///
833 /// It is [`Self::instances_from_data`] for a face that [`Self::from_data_with_remote_id`]
834 /// describes: every instance answers `remote_id` from [`Self::remote_id`].
835 pub fn instances_from_data_with_remote_id(
836 data: FontData,
837 face_index: u32,
838 remote_id: u64,
839 ) -> Vec<Font> {
840 Self::parse_instances(data, face_index, Some(remote_id))
841 }
842
843 /// [`Self::from_data`], with the face's remote id if it has one.
844 fn parse_face(data: FontData, face_index: u32, remote_id: Option<u64>) -> Option<Font> {
845 let (data, face_index) = unwrapped(data, face_index)?;
846 Self::instance(data, face_index, remote_id, Vec::new())
847 }
848
849 /// [`Self::instances_from_data`], with the face's remote id if it has one.
850 fn parse_instances(data: FontData, face_index: u32, remote_id: Option<u64>) -> Vec<Font> {
851 let Some((data, face_index)) = unwrapped(data, face_index) else {
852 return Vec::new();
853 };
854 let instances = named_instance_coordinates((*data).as_ref(), face_index);
855 if instances.is_empty() {
856 return Self::instance(data, face_index, remote_id, Vec::new())
857 .into_iter()
858 .collect();
859 }
860 // The expensive halves (cmap, shaping caches) parse ONCE; each
861 // instance adds only its metrics and axis data.
862 let Some(shared) = SharedFace::parse(data, face_index, remote_id) else {
863 return Vec::new();
864 };
865 instances
866 .into_iter()
867 .filter_map(|coordinates| Self::shared_instance(shared.clone(), coordinates))
868 .collect()
869 }
870
871 /// One face at one set of coordinates, self-described from its tables
872 /// (attrs overridden by the coordinates' weight/italic axes).
873 fn instance(
874 data: FontData,
875 face_index: u32,
876 remote_id: Option<u64>,
877 coordinates: Vec<([u8; 4], f32)>,
878 ) -> Option<Font> {
879 let shared = SharedFace::parse(data, face_index, remote_id)?;
880 Self::shared_instance(shared, coordinates)
881 }
882
883 /// [`Self::instance`] over already-shared file state.
884 fn shared_instance(shared: Arc<SharedFace>, coordinates: Vec<([u8; 4], f32)>) -> Option<Font> {
885 let bytes: &[u8] = (*shared.data).as_ref();
886 let (family, aliases) = embedded_names(bytes, shared.face_index)?;
887 let attrs = instance_attrs(embedded_attrs(bytes, shared.face_index), &coordinates);
888 let mut font = Font::at_coordinates(shared, &family, attrs, coordinates)?;
889 for name in &aliases {
890 font.add_alias(name);
891 }
892 Some(font)
893 }
894}
895
896impl SharedFace {
897 fn parse(data: FontData, face_index: u32, remote_id: Option<u64>) -> Option<Arc<Self>> {
898 let bytes: &[u8] = (*data).as_ref();
899 let font = skrifa::FontRef::from_index(bytes, face_index).ok()?;
900 let charmap = font
901 .charmap()
902 .mappings()
903 .map(|(code, glyph)| (code, glyph.to_u32()))
904 .collect();
905 let harf = harfrust::FontRef::from_index(bytes, face_index).ok()?;
906 let shaper_data = harfrust::ShaperData::new(&harf);
907 Some(Arc::new(Self {
908 data,
909 face_index,
910 remote_id,
911 charmap,
912 shaper_data,
913 }))
914 }
915}
916
917/// The (tag, value) coordinate rows of every fvar named instance.
918fn named_instance_coordinates(bytes: &[u8], face_index: u32) -> Vec<Vec<([u8; 4], f32)>> {
919 let Ok(font) = skrifa::FontRef::from_index(bytes, face_index) else {
920 return Vec::new();
921 };
922 let axis_tags: Vec<[u8; 4]> = font
923 .axes()
924 .iter()
925 .map(|axis| axis.tag().to_be_bytes())
926 .collect();
927 font.named_instances()
928 .iter()
929 .map(|instance| {
930 axis_tags
931 .iter()
932 .copied()
933 .zip(instance.user_coords())
934 .collect()
935 })
936 .collect()
937}
938
939/// A named instance's place in its family: the weight/italic/width axes
940/// override what the file's default-instance OS/2 table says.
941fn instance_attrs(base: FontAttrs, coordinates: &[([u8; 4], f32)]) -> FontAttrs {
942 let mut attrs = base;
943 for (tag, value) in coordinates {
944 match tag {
945 b"wght" => attrs.weight = value.clamp(1.0, 1000.0) as u16,
946 b"ital" => attrs.italic = *value >= 0.5,
947 b"slnt" => attrs.italic = attrs.italic || *value < 0.0,
948 // `wdth` is already a percentage, which is what CSS asks for.
949 b"wdth" => attrs.stretch = value.clamp(1.0, 1000.0),
950 _ => {}
951 }
952 }
953 attrs
954}
955
956/// How far `candidate` is from `wanted`, as the sort key of CSS font
957/// matching's precedence: width, then style, then weight.
958fn style_distance(candidate: FontAttrs, wanted: FontAttrs) -> (u32, bool, u16) {
959 (
960 stretch_distance(candidate.stretch, wanted.stretch),
961 candidate.italic != wanted.italic,
962 candidate.weight.abs_diff(wanted.weight),
963 )
964}
965
966/// Ordered width distance for variant matching. Quantized to 1/16 of a
967/// percent so it can be an integer sort key without collapsing the named
968/// widths (they are 12.5 apart at the closest).
969fn stretch_distance(candidate: f32, wanted: f32) -> u32 {
970 ((candidate - wanted).abs() * 16.0) as u32
971}
972
973/// WOFF2 arrives brotli-wrapped; faces parse the unwrapped TrueType bytes
974/// (icon and web fonts ship compressed — the CoreText managers accept
975/// them, so registration here does too). Identity for everything else.
976/// A collection face is emitted as its own single-face SFNT, so the
977/// returned index is always zero after unwrap.
978#[cfg(feature = "woff2")]
979fn unwrapped(data: FontData, face_index: u32) -> Option<(FontData, u32)> {
980 let bytes: &[u8] = (*data).as_ref();
981 if !bytes.starts_with(b"wOF2") {
982 return Some((data, face_index));
983 }
984 let unpacked = crate::woff2::to_sfnt(bytes, face_index)?;
985 Some((Arc::new(unpacked), 0))
986}
987
988#[cfg(not(feature = "woff2"))]
989fn unwrapped(data: FontData, face_index: u32) -> Option<(FontData, u32)> {
990 Some((data, face_index))
991}
992
993/// name table: the primary is the en typographic family (then en family,
994/// then any-language); every other family/typographic-family string rides
995/// along as an alias.
996fn embedded_names(data: &[u8], face_index: u32) -> Option<(String, Vec<String>)> {
997 use swash::StringId;
998 let font = swash::FontRef::from_index(data, face_index as usize)?;
999 let strings = font.localized_strings();
1000 let pick = |id: StringId| {
1001 strings
1002 .find_by_id(id, Some("en"))
1003 .or_else(|| strings.find_by_id(id, None))
1004 .map(|s| s.to_string())
1005 };
1006 let primary = pick(StringId::TypographicFamily).or_else(|| pick(StringId::Family))?;
1007 let aliases = strings
1008 .filter(|s| matches!(s.id(), StringId::Family | StringId::TypographicFamily))
1009 .map(|s| s.to_string())
1010 .filter(|name| *name != primary)
1011 .collect();
1012 Some((primary, aliases))
1013}
1014
1015/// OS/2 weight + width + style flags via swash attributes.
1016fn embedded_attrs(data: &[u8], face_index: u32) -> FontAttrs {
1017 let Some(font) = swash::FontRef::from_index(data, face_index as usize) else {
1018 return FontAttrs::default();
1019 };
1020 let attrs = font.attributes();
1021 FontAttrs {
1022 weight: attrs.weight().0,
1023 italic: attrs.style() != swash::Style::Normal,
1024 stretch: attrs.stretch().to_percentage(),
1025 }
1026}
1027
1028/// Unicode Private Use Areas — codepoints whose meaning belongs to a
1029/// specific registered font, never to a generic fallback source.
1030fn is_private_use(codepoint: char) -> bool {
1031 matches!(
1032 codepoint,
1033 '\u{E000}'..='\u{F8FF}' | '\u{F0000}'..='\u{FFFFD}' | '\u{100000}'..='\u{10FFFD}'
1034 )
1035}
1036
1037/// `FontCollection` owns registered faces and sources for resolving missing fonts.
1038///
1039/// Paragraph building consults sources in order and registers their answers.
1040/// Unanswered requests accumulate until [`Self::take_unanswered`] is called.
1041#[derive(Default)]
1042pub struct FontCollection {
1043 faces: FaceSet,
1044 /// Consulted in order, first answer wins (Skia's manager priority:
1045 /// registered bytes and downloaders before the platform database).
1046 sources: Vec<Box<dyn FontSource>>,
1047 /// Misses no source answered, since the last drain.
1048 unanswered: FontDemand,
1049}
1050
1051impl FontCollection {
1052 /// `new` creates an empty collection with no font sources.
1053 pub fn new() -> FontCollection {
1054 FontCollection::default()
1055 }
1056
1057 /// `faces` returns the faces currently registered in the collection.
1058 ///
1059 /// A built paragraph clones this set and remains independent of later changes.
1060 pub fn faces(&self) -> &FaceSet {
1061 &self.faces
1062 }
1063
1064 /// `register` adds font bytes under a family, with the weight, slant and width the file
1065 /// declares.
1066 ///
1067 /// The face answers only to `family`, as [`FaceSet::register`] describes. It returns
1068 /// `None` when face zero cannot be parsed.
1069 pub fn register(&mut self, family: &str, bytes: Vec<u8>) -> Option<FontId> {
1070 self.faces.register(family, bytes)
1071 }
1072
1073 /// `add` registers an already parsed font.
1074 pub fn add(&mut self, font: Font) -> FontId {
1075 self.faces.add(font)
1076 }
1077
1078 /// `add_fallback` appends a registered font to the global fallback order.
1079 ///
1080 /// `id` must belong to this collection.
1081 pub fn add_fallback(&mut self, id: FontId) {
1082 self.faces.add_fallback(id);
1083 }
1084
1085 /// `get` returns a registered font by identifier.
1086 ///
1087 /// # Panics
1088 ///
1089 /// Panics if `id` does not belong to this collection.
1090 pub fn get(&self, id: FontId) -> &Font {
1091 self.faces.get(id)
1092 }
1093
1094 /// `len` returns the number of registered fonts.
1095 pub fn len(&self) -> usize {
1096 self.faces.len()
1097 }
1098
1099 /// `family` returns the first font matching a family name or alias.
1100 pub fn family(&self, name: &str) -> Option<FontId> {
1101 self.faces.family(name)
1102 }
1103
1104 /// `add_source` appends a source consulted when registered fonts cannot satisfy a request.
1105 pub fn add_source(&mut self, source: impl FontSource + 'static) {
1106 self.sources.push(Box::new(source));
1107 }
1108
1109 /// `add_boxed_source` appends an already boxed font source.
1110 pub fn add_boxed_source(&mut self, source: Box<dyn FontSource>) {
1111 self.sources.push(source);
1112 }
1113
1114 /// `is_empty` reports whether no fonts are currently registered.
1115 pub fn is_empty(&self) -> bool {
1116 self.faces.is_empty()
1117 }
1118
1119 /// `adopt_faces` replaces the registered faces with a prepared set.
1120 ///
1121 /// This supports hosts that answer [`FontDemand`] using [`FaceSet::grown_by`].
1122 pub fn adopt_faces(&mut self, faces: FaceSet) {
1123 self.faces = faces;
1124 }
1125
1126 /// `take_unanswered` drains font requests that no source could satisfy.
1127 ///
1128 /// Hosts may load and [`Self::register`] matching fonts before rebuilding
1129 /// affected paragraphs.
1130 pub fn take_unanswered(&mut self) -> FontDemand {
1131 std::mem::take(&mut self.unanswered)
1132 }
1133
1134 /// `require_family` makes sure the family `name` has a face, asking the
1135 /// sources when none is registered.
1136 ///
1137 /// The sources are asked in order and what they answer is registered
1138 /// (Skia's `findTypefaces` walking its managers). It returns `false` when
1139 /// nobody had the family, and records the miss for
1140 /// [`Self::take_unanswered`].
1141 pub fn require_family(&mut self, name: &str) -> bool {
1142 if self.faces.family(name).is_some() {
1143 return true;
1144 }
1145 for source in &mut self.sources {
1146 let faces = source.family(name);
1147 if self.faces.register_answers(faces, name) {
1148 return true;
1149 }
1150 }
1151 self.unanswered.add_family(name);
1152 false
1153 }
1154
1155 /// `require_codepoint` makes sure a face that `families` and `attrs`
1156 /// resolve to covers `codepoint`, asking the sources for a fallback face
1157 /// when none does.
1158 ///
1159 /// It is the per-character half of [`Self::require_family`] (Skia's
1160 /// `defaultFallback(unicode, ..)`): `false` means no source covered the
1161 /// character, and the miss is recorded for [`Self::take_unanswered`].
1162 pub fn require_codepoint(
1163 &mut self,
1164 families: &[String],
1165 codepoint: char,
1166 attrs: FontAttrs,
1167 ) -> bool {
1168 if self.faces.covered_by_resolution(families, attrs, codepoint) {
1169 return true;
1170 }
1171 for index in 0..self.sources.len() {
1172 let (head, tail) = self.sources.split_at_mut(index);
1173 let _ = head;
1174 let source = &mut tail[0];
1175 if self
1176 .faces
1177 .register_fallback_answer(source.as_mut(), codepoint, attrs)
1178 {
1179 return true;
1180 }
1181 }
1182 self.unanswered.add_codepoint(codepoint, attrs);
1183 false
1184 }
1185}
1186
1187#[cfg(test)]
1188mod tests {
1189 use super::*;
1190
1191 fn fira() -> FontData {
1192 let path = concat!(
1193 env!("CARGO_MANIFEST_DIR"),
1194 "/../../assets/fonts/fira_sans.ttf"
1195 );
1196 Arc::new(std::fs::read(path).unwrap())
1197 }
1198
1199 /// A variable face's named instances are built over its one shared
1200 /// parse, each at its own coordinates — the path `parse_instances`
1201 /// takes, driven here directly because no test font is variable. The
1202 /// process rendering the glyphs tells instances apart by their
1203 /// coordinates, so every one must still name the face it came from.
1204 #[test]
1205 fn named_instances_answer_their_face_remote_id() {
1206 let shared = SharedFace::parse(fira(), 0, Some(7)).unwrap();
1207 for weight in [300.0, 700.0] {
1208 let instance = Font::shared_instance(shared.clone(), vec![(*b"wght", weight)]).unwrap();
1209 assert_eq!(instance.remote_id(), Some(7));
1210 assert_eq!(instance.variation_coordinates(), &[(*b"wght", weight)]);
1211 }
1212 }
1213}