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