From 3c495a7b611d14169b85b2e80f4e9676c3a20e8f Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 11 Nov 2026 20:22:05 +0000 Subject: [PATCH] Fonts: the file search for an uncovered character stops at a budget between scans --- crates/text/src/fontdb.rs | 11 +++++++++++ crates/text/src/fontdb/db.rs | 1 + crates/text/src/fontdb/lookup.rs | 7 +++++++ crates/text/src/fontdb/names.rs | 8 ++++++++ 4 files changed, 27 insertions(+) diff --git a/crates/text/src/fontdb.rs b/crates/text/src/fontdb.rs index a95ac2dc5..bad992e51 100644 --- a/crates/text/src/fontdb.rs +++ b/crates/text/src/fontdb.rs @@ -192,6 +192,17 @@ mod alias_tests { font } + #[cfg(not(target_arch = "wasm32"))] + #[test] + fn the_file_search_for_uncovered_characters_stops_at_its_budget() { + let db = FontDb::with_font_dirs(vec![]); + db.sys.lock().unwrap().file_searches = names::MAX_FILE_SEARCHES; + assert!(!db.system_fallback('\u{E000}')); + let sys = db.sys.lock().unwrap(); + assert_eq!(sys.file_searches, names::MAX_FILE_SEARCHES); + assert!(!sys.misses.contains(&'\u{E000}'), "an unsearched character is not a remembered miss"); + } + #[test] fn a_collection_lists_at_most_max_faces() { let mut font = name_font(&[(3, 0x0409, 1, "Fam"), (3, 0x0409, 2, "Regular")]); diff --git a/crates/text/src/fontdb/db.rs b/crates/text/src/fontdb/db.rs index 902a3e5aa..4ef17c40c 100644 --- a/crates/text/src/fontdb/db.rs +++ b/crates/text/src/fontdb/db.rs @@ -456,6 +456,7 @@ impl FontDb { let mut sys = self.sys.lock().unwrap_or_else(|e| e.into_inner()); sys.scans = sys.scans.wrapping_add(1); sys.misses.clear(); + sys.file_searches = 0; } self.changed(); n diff --git a/crates/text/src/fontdb/lookup.rs b/crates/text/src/fontdb/lookup.rs index 71e8e7ab8..18af244ed 100644 --- a/crates/text/src/fontdb/lookup.rs +++ b/crates/text/src/fontdb/lookup.rs @@ -501,6 +501,13 @@ impl FontDb { return true; } } + { + let mut sys = self.sys.lock().unwrap_or_else(|e| e.into_inner()); + if sys.file_searches >= super::names::MAX_FILE_SEARCHES { + return false; + } + sys.file_searches += 1; + } let mut paths: Vec = self.read_catalog().values().flat_map(|c| c.faces.iter().map(|cf| cf.path.clone())).collect(); paths.sort(); paths.dedup(); diff --git a/crates/text/src/fontdb/names.rs b/crates/text/src/fontdb/names.rs index 6ed1b44a2..7531b130e 100644 --- a/crates/text/src/fontdb/names.rs +++ b/crates/text/src/fontdb/names.rs @@ -7,8 +7,16 @@ pub(super) struct SysFallback { pub(super) misses: std::collections::HashSet, /// Counts the scans of the font folders: a search that a scan overtook doesn't record its miss. pub(super) scans: u64, + /// Characters that went on to read every cataloged font file (and found nothing, or a face). + /// Each such search rereads the catalog, so only [`MAX_FILE_SEARCHES`] are made per scan. + pub(super) file_searches: u32, } +/// The most characters searched for in the font files themselves between two scans of the font +/// folders. Real text has a few uncovered characters; a document of thousands of distinct +/// private-use code points would reread the whole catalog for each. +pub(super) const MAX_FILE_SEARCHES: u32 = 1024; + /// Fonts made for Thai, tried first (when installed) for Thai the font in use lacks: Windows /// (Leelawadee UI; Tahoma before Windows 8), macOS (Thonburi, Sukhumvit Set) and Linux (Noto, /// TLWG).