Skip to main content

inset_embedder/
fonts.rs

1//! The font collection a paragraph shapes against. Flutter's engine owns one per process and
2//! asks the OS for faces; here the collection is the host's [`FontCollection`], filled from
3//! [`Platform::font_source`](crate::Platform::font_source), with the faces of
4//! [`Platform::default_font_families`](crate::Platform::default_font_families) as its fallback
5//! chain, and from whatever the application registers, and handed to
6//! [`ParagraphBuilder::build`](crate::ParagraphBuilder::build).
7
8use valo::{Font, FontAttrs};
9pub use valo::{FontCollection, FontDemand, FontId, FontSource};
10use valo_system_fonts::SystemFonts;
11pub use valo_system_fonts::fontmgr::{FontManager, Typeface};
12
13use crate::TargetPlatform;
14
15/// The engine's font manager: the platform's installed fonts through a [`FontManager`], and
16/// the two family names Flutter's engine defines for the platform's own user-interface font.
17///
18/// `CupertinoSystemText` is that font at text sizes and `CupertinoSystemDisplay` at display
19/// sizes, each answered with every weight the platform has, as the engine registers them
20/// (`txt/platform_mac.mm`: the display face at its 29-point breakpoint, the text face by
21/// the default fallback). Every other family, and character fallback, goes to the manager.
22pub struct SystemFontSource {
23    fonts: SystemFonts,
24}
25
26/// Flutter's `CupertinoSystemText`, answered by the engine's default fallback at text sizes.
27const CUPERTINO_TEXT: &str = "CupertinoSystemText";
28/// Flutter's `CupertinoSystemDisplay`, the system font at and above the display breakpoint.
29const CUPERTINO_DISPLAY: &str = "CupertinoSystemDisplay";
30/// The point size the engine's default text-size lookup is made at.
31const TEXT_SIZE: f32 = 17.0;
32/// The engine's `kSFProDisplayBreakPoint`.
33const DISPLAY_SIZE: f32 = 29.0;
34
35impl SystemFontSource {
36    /// Over `manager`: a host's own lookup, or one that answers across a boundary.
37    pub fn new(manager: Box<dyn FontManager>) -> SystemFontSource {
38        SystemFontSource {
39            fonts: SystemFonts::with_manager(manager),
40        }
41    }
42
43    /// Over this platform's own font manager.
44    #[cfg(not(target_arch = "wasm32"))]
45    pub fn platform() -> SystemFontSource {
46        SystemFontSource {
47            fonts: SystemFonts::load(),
48        }
49    }
50
51    /// The languages character fallback prefers, BCP 47, most preferred first (Flutter's
52    /// `PlatformDispatcher.locales`).
53    pub fn set_locales(&mut self, locales: Vec<String>) {
54        self.fonts.set_locales(locales);
55    }
56}
57
58impl FontSource for SystemFontSource {
59    fn family(&mut self, name: &str) -> Vec<Font> {
60        match name {
61            CUPERTINO_TEXT => self.fonts.system_family(TEXT_SIZE),
62            CUPERTINO_DISPLAY => self.fonts.system_family(DISPLAY_SIZE),
63            _ => self.fonts.family(name),
64        }
65    }
66
67    fn face_for_codepoint(&mut self, codepoint: char, attrs: FontAttrs) -> Option<Font> {
68        self.fonts.face_for_codepoint(codepoint, attrs)
69    }
70}
71
72/// Flutter `txt::GetDefaultFontFamilies()` of the engine built for `os`, from that OS's file
73/// (`txt/platform_mac.mm`, `platform_linux.cc`, …): the families text is drawn in when its style
74/// names none, first match first.
75///
76/// On Apple's systems Flutter reads `[systemFontOfSize:14].familyName`. That face is what
77/// [`SystemFontSource`] answers as `CupertinoSystemText`, so that is the name given here.
78pub fn default_font_families(os: TargetPlatform) -> Vec<String> {
79    let families: &[&str] = match os {
80        TargetPlatform::Android => &["sans-serif"],
81        TargetPlatform::Fuchsia => &["Roboto"],
82        TargetPlatform::IOS | TargetPlatform::MacOS => &[CUPERTINO_TEXT],
83        TargetPlatform::Linux => &[
84            "Ubuntu",
85            "Adwaita Sans",
86            "Cantarell",
87            "DejaVu Sans",
88            "Liberation Sans",
89            "Arial",
90        ],
91        TargetPlatform::Windows => &["Segoe UI", "Arial"],
92    };
93    families.iter().map(|&family| family.to_owned()).collect()
94}
95
96/// Flutter `skia::textlayout::FontCollection::setDefaultFontManager`: register the faces of
97/// `families`, the platform's default families, as the collection's fallback chain, then keep
98/// `source` for later lookups.
99///
100/// Valo has no default font manager. An empty style `families` list walks this fallback
101/// chain, then `FontId(0)`. Without the chain, a later `register` (the Cupertino icon font)
102/// becomes font 0 and unspecified-family text paints `.notdef`.
103pub fn set_default_font_manager(
104    fonts: &mut FontCollection,
105    mut source: Box<dyn FontSource>,
106    families: &[String],
107) {
108    for name in families {
109        for mut font in source.family(name) {
110            font.add_alias(name);
111            let id = fonts.add(font);
112            fonts.add_fallback(id);
113        }
114    }
115    fonts.add_boxed_source(source);
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::Platform;
122
123    #[test]
124    fn default_font_families_match_the_engine_on_this_os() {
125        let names = crate::InertPlatform.default_font_families();
126        assert!(!names.is_empty());
127        #[cfg(any(target_os = "macos", target_os = "ios"))]
128        assert_eq!(names, vec![CUPERTINO_TEXT]);
129        #[cfg(target_os = "android")]
130        assert_eq!(names, vec!["sans-serif"]);
131    }
132}