Skip to main content

blitz_dom/
font_fallback.rs

1//! Embedder-supplied family fallback and helpers for registering compiled-in faces.
2//!
3//! blitz-dom carries no font bytes or family names here. An embedder that bundles
4//! fonts (a headless build without system fonts) sets
5//! [`DocumentConfig::family_fallback`](crate::DocumentConfig::family_fallback) to map
6//! a CSS family name such as "Arial" to a family it registered. The author name stays
7//! first in every query, so a page `@font-face` under that name still wins; the mapped
8//! name is appended only when it differs.
9//!
10//! The mapping is process-wide: the first document created with a fallback sets it,
11//! and sub-documents (iframes) created without one use it too.
12
13use std::sync::{Arc, OnceLock};
14
15use parley::fontique::{Blob, FamilyId, FontInfoOverride, GenericFamily, Script};
16
17use crate::FontContext;
18
19/// Maps a CSS family name to the family an embedder registered for it.
20pub type FamilyFallback = fn(&str) -> Option<&'static str>;
21
22static FAMILY_FALLBACK: OnceLock<FamilyFallback> = OnceLock::new();
23
24pub(crate) fn set(fallback: FamilyFallback) {
25    let _ = FAMILY_FALLBACK.set(fallback);
26}
27
28/// The fallback family for `name`, unless it is `name` itself.
29pub(crate) fn lookup(name: &str) -> Option<&'static str> {
30    let mapped = FAMILY_FALLBACK.get()?(name)?;
31    (!mapped.eq_ignore_ascii_case(name)).then_some(mapped)
32}
33
34/// Register bytes under the family name stored in the font.
35pub fn register_face(ctx: &mut FontContext, bytes: &'static [u8]) {
36    ctx.collection
37        .register_fonts(Blob::new(Arc::new(bytes) as _), None);
38}
39
40/// Register bytes under `family`, keeping the file's weight and style.
41///
42/// A second call with the same family appends another face. Register regular before
43/// bold and italic so an exact weight match is the real face. `None` when the bytes
44/// hold no font.
45pub fn register_family(
46    ctx: &mut FontContext,
47    family: &str,
48    bytes: &'static [u8],
49) -> Option<FamilyId> {
50    let registered = ctx.collection.register_fonts(
51        Blob::new(Arc::new(bytes) as _),
52        Some(FontInfoOverride {
53            family_name: Some(family),
54            ..FontInfoOverride::default()
55        }),
56    );
57    registered.into_iter().next().map(|(id, _)| id)
58}
59
60/// Bind the CSS generics to registered families, each followed by `extra` (script
61/// fallbacks such as Devanagari), and append `extra` to the fallbacks of `scripts`.
62///
63/// A glyph stops at the first family that has it, so `extra` does not change Latin
64/// widths; it lets a run in another script resolve when the locale is untracked,
65/// since fontique does not fall through from an untracked locale to the default
66/// script bucket.
67pub fn bind(
68    ctx: &mut FontContext,
69    sans: FamilyId,
70    serif: FamilyId,
71    mono: FamilyId,
72    extra: &[FamilyId],
73    scripts: &[[u8; 4]],
74) {
75    let with_extra = |primary: FamilyId| std::iter::once(primary).chain(extra.iter().copied());
76    for generic in [
77        GenericFamily::SansSerif,
78        GenericFamily::SystemUi,
79        GenericFamily::UiSansSerif,
80    ] {
81        ctx.collection
82            .set_generic_families(generic, with_extra(sans));
83    }
84    for generic in [GenericFamily::Serif, GenericFamily::UiSerif] {
85        ctx.collection
86            .set_generic_families(generic, with_extra(serif));
87    }
88    for generic in [GenericFamily::Monospace, GenericFamily::UiMonospace] {
89        ctx.collection
90            .set_generic_families(generic, with_extra(mono));
91    }
92    for script in scripts {
93        ctx.collection
94            .append_fallbacks(Script::from_bytes(*script), extra.iter().copied());
95    }
96}