From a67dded054337abe7af5da340595efb8938b26d8 Mon Sep 17 00:00:00 2001 From: tigregalis Date: Mon, 3 Mar 2025 16:14:45 +0800 Subject: [PATCH] Use fallback list via dyn trait in FontSystem --- src/font/fallback/macos.rs | 29 ++++++++++++++++++++++++++--- src/font/fallback/mod.rs | 24 ++++++++++++++++++++---- src/font/fallback/other.rs | 29 ++++++++++++++++++++++++++--- src/font/fallback/unix.rs | 29 ++++++++++++++++++++++++++--- src/font/fallback/windows.rs | 29 ++++++++++++++++++++++++++--- src/font/system.rs | 22 ++++++++++++++++++---- 6 files changed, 142 insertions(+), 20 deletions(-) diff --git a/src/font/fallback/macos.rs b/src/font/fallback/macos.rs index 1bd658a..62b2590 100644 --- a/src/font/fallback/macos.rs +++ b/src/font/fallback/macos.rs @@ -2,8 +2,31 @@ use unicode_script::Script; +use super::Fallback; + +/// A platform-specific font fallback list, for MacOS. +pub struct PlatformFallback; + +impl Fallback for PlatformFallback { + fn common_fallback(&self) -> &'static [&'static str] { + common_fallback() + } + + fn forbidden_fallback(&self) -> &'static [&'static str] { + forbidden_fallback() + } + + fn script_fallback( + &self, + script: unicode_script::Script, + locale: &str, + ) -> &'static [&'static str] { + script_fallback(script, locale) + } +} + // Fallbacks to use after any script specific fallbacks -pub fn common_fallback() -> &'static [&'static str] { +fn common_fallback() -> &'static [&'static str] { &[ ".SF NS", "Menlo", @@ -14,7 +37,7 @@ pub fn common_fallback() -> &'static [&'static str] { } // Fallbacks to never use -pub fn forbidden_fallback() -> &'static [&'static str] { +fn forbidden_fallback() -> &'static [&'static str] { &[".LastResort"] } @@ -34,7 +57,7 @@ fn han_unification(locale: &str) -> &'static [&'static str] { } // Fallbacks to use per script -pub fn script_fallback(script: Script, locale: &str) -> &'static [&'static str] { +fn script_fallback(script: Script, locale: &str) -> &'static [&'static str] { //TODO: abstract style (sans/serif/monospaced) //TODO: pull more data from about:config font.name-list.sans-serif in Firefox match script { diff --git a/src/font/fallback/mod.rs b/src/font/fallback/mod.rs index 2aa1479..5050c93 100644 --- a/src/font/fallback/mod.rs +++ b/src/font/fallback/mod.rs @@ -25,6 +25,19 @@ mod platform; #[path = "windows.rs"] mod platform; +pub trait Fallback { + /// Fallbacks to use after any script specific fallbacks + fn common_fallback(&self) -> &'static [&'static str]; + + /// Fallbacks to never use + fn forbidden_fallback(&self) -> &'static [&'static str]; + + /// Fallbacks to use per script + fn script_fallback(&self, script: Script, locale: &str) -> &'static [&'static str]; +} + +pub use platform::PlatformFallback; + #[cfg(not(feature = "warn_on_missing_glyphs"))] use log::debug as missing_warn; #[cfg(feature = "warn_on_missing_glyphs")] @@ -94,7 +107,7 @@ impl<'a> FontFallbackIter<'a> { word ); } else if !self.scripts.is_empty() && self.common_i > 0 { - let family = common_fallback()[self.common_i - 1]; + let family = self.font_system.fallbacks.common_fallback()[self.common_i - 1]; missing_warn!( "Failed to find script fallback for {:?} locale '{}', used '{}': '{}'", self.scripts, @@ -262,7 +275,10 @@ impl Iterator for FontFallbackIter<'_> { while self.script_i.0 < self.scripts.len() { let script = self.scripts[self.script_i.0]; - let script_families = script_fallback(script, self.font_system.locale()); + let script_families = self + .font_system + .fallbacks + .script_fallback(script, self.font_system.locale()); while self.script_i.1 < script_families.len() { let script_family = script_families[self.script_i.1]; self.script_i.1 += 1; @@ -285,7 +301,7 @@ impl Iterator for FontFallbackIter<'_> { self.script_i.1 = 0; } - let common_families = common_fallback(); + let common_families = self.font_system.fallbacks.common_fallback(); while self.common_i < common_families.len() { let common_family = common_families[self.common_i]; self.common_i += 1; @@ -301,7 +317,7 @@ impl Iterator for FontFallbackIter<'_> { //TODO: do we need to do this? //TODO: do not evaluate fonts more than once! - let forbidden_families = forbidden_fallback(); + let forbidden_families = self.font_system.fallbacks.forbidden_fallback(); while self.other_i < self.font_match_keys.len() { let id = self.font_match_keys[self.other_i].id; self.other_i += 1; diff --git a/src/font/fallback/other.rs b/src/font/fallback/other.rs index 223da76..4ab2c16 100644 --- a/src/font/fallback/other.rs +++ b/src/font/fallback/other.rs @@ -2,17 +2,40 @@ use unicode_script::Script; +use super::Fallback; + +/// An empty platform-specific font fallback list. +pub struct PlatformFallback; + +impl Fallback for PlatformFallback { + fn common_fallback(&self) -> &'static [&'static str] { + common_fallback() + } + + fn forbidden_fallback(&self) -> &'static [&'static str] { + forbidden_fallback() + } + + fn script_fallback( + &self, + script: unicode_script::Script, + locale: &str, + ) -> &'static [&'static str] { + script_fallback(script, locale) + } +} + // Fallbacks to use after any script specific fallbacks -pub fn common_fallback() -> &'static [&'static str] { +fn common_fallback() -> &'static [&'static str] { &[] } // Fallbacks to never use -pub fn forbidden_fallback() -> &'static [&'static str] { +fn forbidden_fallback() -> &'static [&'static str] { &[] } // Fallbacks to use per script -pub fn script_fallback(_script: Script, _locale: &str) -> &'static [&'static str] { +fn script_fallback(_script: Script, _locale: &str) -> &'static [&'static str] { &[] } diff --git a/src/font/fallback/unix.rs b/src/font/fallback/unix.rs index 9dd3904..010ff60 100644 --- a/src/font/fallback/unix.rs +++ b/src/font/fallback/unix.rs @@ -2,8 +2,31 @@ use unicode_script::Script; +use super::Fallback; + +/// A platform-specific font fallback list, for Unix. +pub struct PlatformFallback; + +impl Fallback for PlatformFallback { + fn common_fallback(&self) -> &'static [&'static str] { + common_fallback() + } + + fn forbidden_fallback(&self) -> &'static [&'static str] { + forbidden_fallback() + } + + fn script_fallback( + &self, + script: unicode_script::Script, + locale: &str, + ) -> &'static [&'static str] { + script_fallback(script, locale) + } +} + // Fallbacks to use after any script specific fallbacks -pub fn common_fallback() -> &'static [&'static str] { +fn common_fallback() -> &'static [&'static str] { //TODO: abstract style (sans/serif/monospaced) &[ /* Sans-serif fallbacks */ @@ -25,7 +48,7 @@ pub fn common_fallback() -> &'static [&'static str] { } // Fallbacks to never use -pub fn forbidden_fallback() -> &'static [&'static str] { +fn forbidden_fallback() -> &'static [&'static str] { &[] } @@ -45,7 +68,7 @@ fn han_unification(locale: &str) -> &'static [&'static str] { } // Fallbacks to use per script -pub fn script_fallback(script: Script, locale: &str) -> &'static [&'static str] { +fn script_fallback(script: Script, locale: &str) -> &'static [&'static str] { //TODO: abstract style (sans/serif/monospaced) match script { Script::Adlam => &["Noto Sans Adlam", "Noto Sans Adlam Unjoined"], diff --git a/src/font/fallback/windows.rs b/src/font/fallback/windows.rs index c8275bf..1d5abd0 100644 --- a/src/font/fallback/windows.rs +++ b/src/font/fallback/windows.rs @@ -2,8 +2,31 @@ use unicode_script::Script; +use super::Fallback; + +/// A platform-specific font fallback list, for Windows. +pub struct PlatformFallback; + +impl Fallback for PlatformFallback { + fn common_fallback(&self) -> &'static [&'static str] { + common_fallback() + } + + fn forbidden_fallback(&self) -> &'static [&'static str] { + forbidden_fallback() + } + + fn script_fallback( + &self, + script: unicode_script::Script, + locale: &str, + ) -> &'static [&'static str] { + script_fallback(script, locale) + } +} + // Fallbacks to use after any script specific fallbacks -pub fn common_fallback() -> &'static [&'static str] { +fn common_fallback() -> &'static [&'static str] { //TODO: abstract style (sans/serif/monospaced) &[ "Segoe UI", @@ -15,7 +38,7 @@ pub fn common_fallback() -> &'static [&'static str] { } // Fallbacks to never use -pub fn forbidden_fallback() -> &'static [&'static str] { +fn forbidden_fallback() -> &'static [&'static str] { &[] } @@ -36,7 +59,7 @@ fn han_unification(locale: &str) -> &'static [&'static str] { } // Fallbacks to use per script -pub fn script_fallback(script: Script, locale: &str) -> &'static [&'static str] { +fn script_fallback(script: Script, locale: &str) -> &'static [&'static str] { //TODO: better match https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/platform/fonts/win/font_fallback_win.cc#L99 match script { Script::Adlam => &["Ebrima"], diff --git a/src/font/system.rs b/src/font/system.rs index 87931f8..13e128c 100644 --- a/src/font/system.rs +++ b/src/font/system.rs @@ -1,4 +1,5 @@ use crate::{Attrs, Font, FontMatchAttrs, HashMap, ShapeBuffer}; +use alloc::boxed::Box; use alloc::collections::BTreeSet; use alloc::string::String; use alloc::sync::Arc; @@ -10,7 +11,7 @@ use core::ops::{Deref, DerefMut}; pub use fontdb; pub use rustybuzz; -use super::fallback::MonospaceFallbackInfo; +use super::fallback::{Fallback, MonospaceFallbackInfo, PlatformFallback}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct FontMatchKey { @@ -112,6 +113,9 @@ pub struct FontSystem { /// Cache for shaped runs #[cfg(feature = "shape-run-cache")] pub shape_run_cache: crate::ShapeRunCache, + + /// List of fallbacks + pub(crate) fallbacks: Box, } impl fmt::Debug for FontSystem { @@ -150,11 +154,15 @@ impl FontSystem { db.set_sans_serif_family("Open Sans"); db.set_serif_family("DejaVu Serif"); - Self::new_with_locale_and_db(locale, db) + Self::new_with_locale_and_db_and_fallback(locale, db, PlatformFallback) } - /// Create a new [`FontSystem`] with a pre-specified locale and font database. - pub fn new_with_locale_and_db(locale: String, db: fontdb::Database) -> Self { + /// Create a new [`FontSystem`] with a pre-specified locale, font database and font fallback list. + pub fn new_with_locale_and_db_and_fallback( + locale: String, + db: fontdb::Database, + fallbacks: impl Fallback + 'static, + ) -> Self { let mut monospace_font_ids = db .faces() .filter(|face_info| { @@ -204,9 +212,15 @@ impl FontSystem { #[cfg(feature = "shape-run-cache")] shape_run_cache: crate::ShapeRunCache::default(), shape_buffer: ShapeBuffer::default(), + fallbacks: Box::new(fallbacks), } } + /// Create a new [`FontSystem`] with a pre-specified locale and font database. + pub fn new_with_locale_and_db(locale: String, db: fontdb::Database) -> Self { + Self::new_with_locale_and_db_and_fallback(locale, db, PlatformFallback) + } + /// Get the locale. pub fn locale(&self) -> &str { &self.locale