Merge pull request #1376 from storytold/fix/font-fallback-retry-after-rescan

Fonts: a character no font covered is looked for again after a font rescan
This commit is contained in:
BFlat 2026-10-12 16:48:05 +07:00 committed by GitHub
commit fc0f732677
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 1 deletions

View File

@ -389,6 +389,8 @@ fn modified(path: &Path) -> Option<std::time::SystemTime> {
struct SysFallback {
enabled: bool,
misses: std::collections::HashSet<char>,
/// 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
}

View File

@ -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");