From 4251e337c152f79a8758913ad11129156e3ff24e Mon Sep 17 00:00:00 2001 From: bflatastic Date: Mon, 12 Oct 2026 15:20:49 +0700 Subject: [PATCH] Fonts: a character no font covered is looked for again after a font rescan (#992) A character missing from every installed font was remembered as missing for the whole session, so installing a font that has it didn't help until a restart. A rescan of the font folders now forgets those misses, and a search a rescan overtook doesn't record one. Taken from #992 by sanyamsingh001, without its font file probe. Co-authored-by: sanyamsingh001 <314051937+sanyamsingh001@users.noreply.github.com> --- crates/text/src/fontdb.rs | 16 +++++++++++++++- crates/text/src/tests_sysfonts.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/text/src/fontdb.rs b/crates/text/src/fontdb.rs index d1c90716..bd170259 100644 --- a/crates/text/src/fontdb.rs +++ b/crates/text/src/fontdb.rs @@ -389,6 +389,8 @@ fn modified(path: &Path) -> Option { struct SysFallback { enabled: bool, misses: std::collections::HashSet, + /// Counts the scans of the font folders: a search that a scan overtook doesn't record its miss. + scans: u64, } /// Fonts made for Thai, tried first (when installed) for Thai the font in use lacks: Windows @@ -1734,6 +1736,12 @@ impl FontDb { *self.catalog.write().unwrap_or_else(|e| e.into_inner()) = catalog; *self.aliases.write().unwrap_or_else(|e| e.into_inner()) = aliases; *self.postscript.write().unwrap_or_else(|e| e.into_inner()) = postscript; + // A font installed since may cover the characters no font did: they are looked for again. + { + let mut sys = self.sys.lock().unwrap_or_else(|e| e.into_inner()); + sys.scans = sys.scans.wrapping_add(1); + sys.misses.clear(); + } self.changed(); n } @@ -2034,6 +2042,9 @@ impl FontDb { return false; } } + // The first scan done before its count is read, so the first miss is remembered too. + self.ensure_catalog(); + let scans = self.sys.lock().unwrap_or_else(|e| e.into_inner()).scans; let covered = |db: &FontDb| db.read_faces().iter().any(|f| f.covers(c)); for fam in SYSTEM_FALLBACKS { if !self.is_loaded(fam) && self.load_cataloged(fam) && covered(self) { @@ -2055,7 +2066,10 @@ impl FontDb { } } } - self.sys.lock().unwrap_or_else(|e| e.into_inner()).misses.insert(c); + let mut sys = self.sys.lock().unwrap_or_else(|e| e.into_inner()); + if sys.scans == scans { + sys.misses.insert(c); + } false } diff --git a/crates/text/src/tests_sysfonts.rs b/crates/text/src/tests_sysfonts.rs index 009813ca..176ebcb5 100644 --- a/crates/text/src/tests_sysfonts.rs +++ b/crates/text/src/tests_sysfonts.rs @@ -120,6 +120,33 @@ fn fonts_without_outlines_the_engine_draws_are_left_out() { assert_eq!(db.load_system_fonts(), 1); } +/// A character no font covered is looked for again once a rescan finds a font that has it (#992). +#[test] +fn rescanning_retries_characters_no_font_covered() { + // A private-use character none of the bundled fonts has. + const C: char = '\u{F0000}'; + let dir = std::env::temp_dir().join(format!("vc-sysfonts-{}-retry", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + let db = FontDb::with_font_dirs(vec![dir.clone()]); + assert!(db.face_covering(C).is_none()); + // Source Sans 3 Regular renamed, its character map replaced by one holding only `C`. + let base = named(&[(3, EN, 1, "Retry Sans"), (3, EN, 2, "Regular"), (3, EN, 6, "RetrySans-Regular")]); + let font = skrifa::FontRef::new(&base).unwrap(); + let mut builder = write_fonts::FontBuilder::new(); + builder.add_raw(write_fonts::types::Tag::new(b"cmap"), crate::embed::cmap(&[(C as u32, 1)]).unwrap()); + for r in font.table_directory.table_records() { + let tag = write_fonts::types::Tag::new(&r.tag().to_be_bytes()); + if !builder.contains(tag) { + builder.add_raw(tag, font.table_data(r.tag()).unwrap().as_bytes()); + } + } + std::fs::write(dir.join("Retry.ttf"), builder.build()).unwrap(); + assert!(db.face_covering(C).is_none(), "the miss is remembered until the fonts are scanned again"); + assert_eq!(db.load_system_fonts(), 1); + assert_eq!(db.face_covering(C).unwrap().family, "Retry Sans"); +} + #[test] fn rescanning_finds_fonts_installed_since() { let dir = font_dir("rescan");