text: the CJK system-font search no longer keeps every probed font file in memory (#769)

This commit is contained in:
MarekSpodBiedry committed 2026-10-10 23:22:09 +02:00
1 parent ed141c74d0
commit 62358d8c4e
3 files changed
+36 -2

No files matched your search

+34
View File
@@ -215,6 +215,18 @@ impl Face {
pub fn has_char(&self, c: char) -> bool {
self.glyph(c).is_some()
}
/// Whether the face covers every character of `text`. Unlike [`Face::has_char`], a face whose
/// file is not loaded yet is read into a temporary buffer that is dropped afterwards, so
/// probing many system faces does not keep every font file in memory.
pub fn covers_text(&self, text: &str) -> bool {
if let (FaceData::File(p), None) = (&self.info.data, self.bytes.get()) {
let Ok(data) = std::fs::read(p) else { return false };
let Ok(font) = FontRef::from_index(&data, self.info.index) else { return false };
let charmap = font.charmap();
return text.chars().all(|c| charmap.map(c).is_some_and(|g| g.to_u32() != 0));
}
text.chars().all(|c| self.has_char(c))
}
/// Height of the glyph's vertical origin above its baseline, in pixels.
/// OpenType VORG takes precedence; TrueType uses the ink top plus vmtx's top bearing.
pub(crate) fn vertical_origin(&self, gid: u32, px: f32) -> Option<f32> {
@@ -569,4 +581,26 @@ mod tests {
assert!(!f.has_char('\u{5d0}'), "Inter has no Hebrew");
assert!(f.has_feature(b"liga") || f.has_feature(b"calt"));
}
#[test]
fn covers_text_does_not_keep_file_bytes() {
let path = std::env::temp_dir().join(format!("filmcraft-covers-text-{}.ttf", std::process::id()));
std::fs::write(&path, INTER_REGULAR).expect("write temp font");
let info = FaceInfo {
family: "Inter".into(),
style: "Regular".into(),
weight: 400,
italic: false,
index: 0,
data: FaceData::File(path.clone()),
origin: "system",
};
let f = Face { id: 0, info, bytes: OnceLock::new(), shaper: OnceLock::new() };
assert!(f.covers_text("A\u{416}"));
assert!(!f.covers_text("A\u{5d0}"));
assert!(f.bytes.get().is_none(), "probing coverage must not cache the file");
assert!(f.data().is_some());
assert!(f.bytes.get().is_some());
let _ = std::fs::remove_file(&path);
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ fn system_font() -> Option<Arc<FontData>> {
}
filmcraft_text::fonts::scan_system();
let faces: Vec<_> = filmcraft_text::fonts::all_faces().into_iter().filter(|f| f.info.origin == "system" && !f.info.italic).collect();
let covers = |f: &filmcraft_text::fonts::Face| SAMPLE.chars().all(|c| f.has_char(c));
let covers = |f: &filmcraft_text::fonts::Face| f.covers_text(SAMPLE);
let by_weight = |f: &&Arc<filmcraft_text::fonts::Face>| f.info.weight.abs_diff(400);
let preferred = PREFERRED.iter().find_map(|name| faces.iter().filter(|f| f.info.family.eq_ignore_ascii_case(name) && covers(f)).min_by_key(by_weight));
let face = preferred.or_else(|| faces.iter().filter(|f| covers(f)).min_by_key(by_weight))?;
+1 -1
View File
@@ -247,7 +247,7 @@ pub fn system_japanese_font() -> Option<Arc<egui::FontData>> {
FONT.get_or_init(|| {
filmcraft_text::fonts::scan_system();
let faces: Vec<_> = filmcraft_text::fonts::all_faces().into_iter().filter(|f| f.info.origin == "system" && !f.info.italic).collect();
let covers = |f: &filmcraft_text::fonts::Face| JAPANESE_SAMPLE.chars().all(|c| f.has_char(c));
let covers = |f: &filmcraft_text::fonts::Face| f.covers_text(JAPANESE_SAMPLE);
// within a family, the face closest to regular weight
let by_weight = |f: &&Arc<filmcraft_text::fonts::Face>| f.info.weight.abs_diff(400);
let preferred =