Use fallback list via dyn trait in FontSystem

This commit is contained in:
tigregalis 2025-03-03 16:14:45 +08:00 committed by Jeremy Soller
parent 4e6d5d731b
commit a67dded054
6 changed files with 142 additions and 20 deletions

View file

@ -2,8 +2,31 @@
use unicode_script::Script; 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 // Fallbacks to use after any script specific fallbacks
pub fn common_fallback() -> &'static [&'static str] { fn common_fallback() -> &'static [&'static str] {
&[ &[
".SF NS", ".SF NS",
"Menlo", "Menlo",
@ -14,7 +37,7 @@ pub fn common_fallback() -> &'static [&'static str] {
} }
// Fallbacks to never use // Fallbacks to never use
pub fn forbidden_fallback() -> &'static [&'static str] { fn forbidden_fallback() -> &'static [&'static str] {
&[".LastResort"] &[".LastResort"]
} }
@ -34,7 +57,7 @@ fn han_unification(locale: &str) -> &'static [&'static str] {
} }
// Fallbacks to use per script // 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: abstract style (sans/serif/monospaced)
//TODO: pull more data from about:config font.name-list.sans-serif in Firefox //TODO: pull more data from about:config font.name-list.sans-serif in Firefox
match script { match script {

View file

@ -25,6 +25,19 @@ mod platform;
#[path = "windows.rs"] #[path = "windows.rs"]
mod platform; 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"))] #[cfg(not(feature = "warn_on_missing_glyphs"))]
use log::debug as missing_warn; use log::debug as missing_warn;
#[cfg(feature = "warn_on_missing_glyphs")] #[cfg(feature = "warn_on_missing_glyphs")]
@ -94,7 +107,7 @@ impl<'a> FontFallbackIter<'a> {
word word
); );
} else if !self.scripts.is_empty() && self.common_i > 0 { } 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!( missing_warn!(
"Failed to find script fallback for {:?} locale '{}', used '{}': '{}'", "Failed to find script fallback for {:?} locale '{}', used '{}': '{}'",
self.scripts, self.scripts,
@ -262,7 +275,10 @@ impl Iterator for FontFallbackIter<'_> {
while self.script_i.0 < self.scripts.len() { while self.script_i.0 < self.scripts.len() {
let script = self.scripts[self.script_i.0]; 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() { while self.script_i.1 < script_families.len() {
let script_family = script_families[self.script_i.1]; let script_family = script_families[self.script_i.1];
self.script_i.1 += 1; self.script_i.1 += 1;
@ -285,7 +301,7 @@ impl Iterator for FontFallbackIter<'_> {
self.script_i.1 = 0; 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() { while self.common_i < common_families.len() {
let common_family = common_families[self.common_i]; let common_family = common_families[self.common_i];
self.common_i += 1; self.common_i += 1;
@ -301,7 +317,7 @@ impl Iterator for FontFallbackIter<'_> {
//TODO: do we need to do this? //TODO: do we need to do this?
//TODO: do not evaluate fonts more than once! //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() { while self.other_i < self.font_match_keys.len() {
let id = self.font_match_keys[self.other_i].id; let id = self.font_match_keys[self.other_i].id;
self.other_i += 1; self.other_i += 1;

View file

@ -2,17 +2,40 @@
use unicode_script::Script; 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 // 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 // Fallbacks to never use
pub fn forbidden_fallback() -> &'static [&'static str] { fn forbidden_fallback() -> &'static [&'static str] {
&[] &[]
} }
// Fallbacks to use per script // 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] {
&[] &[]
} }

View file

@ -2,8 +2,31 @@
use unicode_script::Script; 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 // 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) //TODO: abstract style (sans/serif/monospaced)
&[ &[
/* Sans-serif fallbacks */ /* Sans-serif fallbacks */
@ -25,7 +48,7 @@ pub fn common_fallback() -> &'static [&'static str] {
} }
// Fallbacks to never use // 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 // 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: abstract style (sans/serif/monospaced)
match script { match script {
Script::Adlam => &["Noto Sans Adlam", "Noto Sans Adlam Unjoined"], Script::Adlam => &["Noto Sans Adlam", "Noto Sans Adlam Unjoined"],

View file

@ -2,8 +2,31 @@
use unicode_script::Script; 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 // 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) //TODO: abstract style (sans/serif/monospaced)
&[ &[
"Segoe UI", "Segoe UI",
@ -15,7 +38,7 @@ pub fn common_fallback() -> &'static [&'static str] {
} }
// Fallbacks to never use // 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 // 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 //TODO: better match https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/platform/fonts/win/font_fallback_win.cc#L99
match script { match script {
Script::Adlam => &["Ebrima"], Script::Adlam => &["Ebrima"],

View file

@ -1,4 +1,5 @@
use crate::{Attrs, Font, FontMatchAttrs, HashMap, ShapeBuffer}; use crate::{Attrs, Font, FontMatchAttrs, HashMap, ShapeBuffer};
use alloc::boxed::Box;
use alloc::collections::BTreeSet; use alloc::collections::BTreeSet;
use alloc::string::String; use alloc::string::String;
use alloc::sync::Arc; use alloc::sync::Arc;
@ -10,7 +11,7 @@ use core::ops::{Deref, DerefMut};
pub use fontdb; pub use fontdb;
pub use rustybuzz; pub use rustybuzz;
use super::fallback::MonospaceFallbackInfo; use super::fallback::{Fallback, MonospaceFallbackInfo, PlatformFallback};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct FontMatchKey { pub struct FontMatchKey {
@ -112,6 +113,9 @@ pub struct FontSystem {
/// Cache for shaped runs /// Cache for shaped runs
#[cfg(feature = "shape-run-cache")] #[cfg(feature = "shape-run-cache")]
pub shape_run_cache: crate::ShapeRunCache, pub shape_run_cache: crate::ShapeRunCache,
/// List of fallbacks
pub(crate) fallbacks: Box<dyn Fallback>,
} }
impl fmt::Debug for FontSystem { impl fmt::Debug for FontSystem {
@ -150,11 +154,15 @@ impl FontSystem {
db.set_sans_serif_family("Open Sans"); db.set_sans_serif_family("Open Sans");
db.set_serif_family("DejaVu Serif"); 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. /// Create a new [`FontSystem`] with a pre-specified locale, font database and font fallback list.
pub fn new_with_locale_and_db(locale: String, db: fontdb::Database) -> Self { 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 let mut monospace_font_ids = db
.faces() .faces()
.filter(|face_info| { .filter(|face_info| {
@ -204,9 +212,15 @@ impl FontSystem {
#[cfg(feature = "shape-run-cache")] #[cfg(feature = "shape-run-cache")]
shape_run_cache: crate::ShapeRunCache::default(), shape_run_cache: crate::ShapeRunCache::default(),
shape_buffer: ShapeBuffer::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. /// Get the locale.
pub fn locale(&self) -> &str { pub fn locale(&self) -> &str {
&self.locale &self.locale