mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-14 19:26:59 -04:00
text: the CJK system-font search no longer keeps every probed font file in memory (#769)
This commit is contained in:
1 parent
ed141c74d0
commit
62358d8c4e
3 files changed
+36
-2
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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))?;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Reference in new issue
Block a user