Fonts: honor the disabled system fallback for symbol and script family loads (#1840)

* Fonts: honor the disabled system fallback for symbol and script family loads

* address review: no catalog loading for loaded families when system fallback is off

* test: cascades load no installed family when system fallback is off
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-11 14:33:15 -05:00
1 parent e03c22c445
commit 46712d0293
3 files changed
+47 -4

No files matched your search

+25 -2
View File
@@ -169,8 +169,14 @@ impl FontDb {
if bundled.contains(fam) {
continue;
}
if self.is_loaded(fam) || self.has_family(fam) {
let f = self.face(fam, style);
if self.is_loaded(fam) || (self.system_fallback_enabled() && self.has_family(fam)) {
// With system fallback off, only a face that is already loaded: `face` could load more catalog files.
let f = if self.system_fallback_enabled() {
self.face(fam, style)
} else {
let Some(f) = self.find(fam, style) else { continue };
f
};
if f.family.eq_ignore_ascii_case(fam) && covers_cluster(&f, cluster) {
return f;
}
@@ -311,4 +317,21 @@ mod tests {
assert!(c.windows(2).all(|w| w[0] <= w[1]));
}
}
/// With system fallback off, the automatic cascades (script families and symbol faces) load no
/// installed family: the result comes from what is already loaded.
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn cascades_load_no_installed_family_when_system_fallback_is_off() {
let db = FontDb::with_font_dirs(Vec::new());
db.set_system_fallback(false);
let base = db.face(crate::DEFAULT_FAMILY, "Regular");
let loaded = |db: &FontDb| [b"Arab", b"Hebr", b"Thai", b"Deva"].into_iter().flat_map(|s| os_families(*s)).filter(|f| db.is_loaded(f)).count();
let before = loaded(&db);
for c in ['\u{0628}', '\u{05D0}', '\u{0E01}', '\u{0905}', '\u{2211}', '\u{2603}'] {
let f = db.cascade(&[c], std::slice::from_ref(&base), "Regular");
assert!(!f.family.is_empty());
}
assert_eq!(loaded(&db), before, "no installed family was loaded");
}
}
+14 -2
View File
@@ -976,6 +976,17 @@ impl FontDb {
let _ = on;
}
/// Whether automatic system-font lookups (fallback by character, symbol and script families)
/// may touch installed fonts. Lookups of a family the document or host names are not gated.
pub(crate) fn system_fallback_enabled(&self) -> bool {
#[cfg(not(target_arch = "wasm32"))]
{
self.sys.lock().unwrap_or_else(std::sync::PoisonError::into_inner).enabled
}
#[cfg(target_arch = "wasm32")]
false
}
/// Process-wide database: the bundled fonts, then the installed system fonts, cataloged the
/// first time a lookup by family name needs them (or ahead of time by
/// [`FontDb::scan_in_background`]).
@@ -1287,7 +1298,7 @@ impl FontDb {
Some(f)
}
fn find(&self, family: &str, style: &str) -> Option<Arc<FontFace>> {
pub(crate) fn find(&self, family: &str, style: &str) -> Option<Arc<FontFace>> {
if style.contains('{') {
{
let faces = self.read_faces();
@@ -1473,7 +1484,8 @@ impl FontDb {
};
#[cfg(not(target_arch = "wasm32"))]
{
let cataloged: Vec<String> = self.read_catalog().iter().map(|e| e.family.clone()).collect();
let cataloged: Vec<String> =
if self.system_fallback_enabled() { self.read_catalog().iter().map(|e| e.family.clone()).collect() } else { Vec::new() };
for fam in SYSTEM_FALLBACKS.iter().filter(|f| f.contains("Symbol")) {
if !self.is_loaded(fam) && cataloged.iter().any(|f| f.eq_ignore_ascii_case(fam)) {
self.load_cataloged(fam);
+8
View File
@@ -425,3 +425,11 @@ fn the_font_scan_skips_fifos_folders_and_link_loops() {
assert!(fifo || n >= 1);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disabling_system_fallback_gates_automatic_family_loads() {
let db = FontDb::with_font_dirs(Vec::new());
assert!(cfg!(target_arch = "wasm32") || db.system_fallback_enabled());
db.set_system_fallback(false);
assert!(!db.system_fallback_enabled());
}