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}