feat(fonts): A family's kind without keeping it loaded
ScopedFonts::family_kind reads a family's format, class, PANOSE and file date from its Regular face. A loaded face is asked; an installed family that isn't loaded is read from its file, which is let go afterwards, instead of loading every family the font menu's filters look at for the rest of the session. Files over MAX_DOCUMENT_FONT_BYTES are skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
03a8d4b955
commit
6baa57b503
|
|
@ -1306,6 +1306,43 @@ impl ScopedFonts<'_> {
|
|||
None
|
||||
}
|
||||
|
||||
/// `family`'s kind (format, class, PANOSE) and when its file was added, read from its Regular
|
||||
/// face (else its first), for the font menus' filters, which look at every family. A face
|
||||
/// already loaded is asked; an installed family that isn't loaded is read from its file, which
|
||||
/// is then let go rather than kept loaded. None for an unknown family or an unreadable file.
|
||||
pub fn family_kind(&self, family: &str) -> Option<(crate::kind::FaceKind, Option<std::time::SystemTime>)> {
|
||||
let styles = self.styles(family);
|
||||
let style = styles.iter().find(|s| *s == "Regular").or(styles.first())?.clone();
|
||||
if self.own().iter().chain(self.db.read_faces().iter()).any(|f| f.family.eq_ignore_ascii_case(family)) {
|
||||
let f = self.find(family, &style)?;
|
||||
return Some((f.kind(), f.added()));
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let path = {
|
||||
let cat = self.db.read_catalog();
|
||||
let of_family = |c: &&CatalogEntry| c.family.eq_ignore_ascii_case(family);
|
||||
let entry = cat.iter().filter(of_family).find(|c| norm(&c.style) == norm(&style)).or_else(|| cat.iter().find(of_family));
|
||||
entry.map(|c| c.path.clone())?
|
||||
};
|
||||
if std::fs::metadata(&path).ok()?.len() > MAX_DOCUMENT_FONT_BYTES {
|
||||
return None;
|
||||
}
|
||||
let data = Arc::new(std::fs::read(&path).ok()?);
|
||||
let found = enumerate_faces(&data);
|
||||
let pick = found
|
||||
.iter()
|
||||
.find(|(_, f, st, _)| f.eq_ignore_ascii_case(family) && norm(st) == norm(&style))
|
||||
.or_else(|| found.iter().find(|(_, f, _, _)| f.eq_ignore_ascii_case(family)))?
|
||||
.clone();
|
||||
let (index, fam, st, coords) = pick;
|
||||
let face = make_face(FontBytes::Owned(data.clone()), FontSource::Installed(path), index, fam, st, coords)?;
|
||||
Some((face.kind(), face.added()))
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
None
|
||||
}
|
||||
|
||||
/// Is `family` available (the document's, loaded, or installed on the system), as named or
|
||||
/// without a format suffix (see [`ScopedFonts::face`])?
|
||||
pub fn has_family(&self, family: &str) -> bool {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,23 @@ fn a_link_back_to_a_parent_folder_ends_the_scan() {
|
|||
assert_eq!(db.load_system_fonts(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_familys_kind_is_read_without_keeping_it_loaded() {
|
||||
let dir = font_dir("kind");
|
||||
let db = FontDb::with_font_dirs(vec![dir]);
|
||||
db.load_system_fonts();
|
||||
let fonts = db.scoped(0);
|
||||
let (kind, added) = fonts.family_kind(FAMILY).expect("an installed family");
|
||||
assert_eq!(kind.class, crate::kind::FontClass::SansSerif);
|
||||
assert!(added.is_some(), "the file's date");
|
||||
assert!(!db.is_loaded(FAMILY), "the file was read and let go");
|
||||
// A loaded family is asked directly, with the same answer.
|
||||
let _ = fonts.face(FAMILY, "Regular");
|
||||
assert!(db.is_loaded(FAMILY));
|
||||
assert_eq!(fonts.family_kind(FAMILY).map(|(k, _)| k.class), Some(crate::kind::FontClass::SansSerif));
|
||||
assert!(fonts.family_kind("No Such Family").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_scan_reads_names_without_loading_the_font() {
|
||||
let dir = font_dir("names");
|
||||
|
|
|
|||
Loading…
Reference in New Issue