Fonts: cap decoded names, bound outline flattening, cache fallback coverage

* fonts: cap decoded names, outline flatten tolerance by size, coverage cache for system fallback

* Apply review fixes

- crates/fonts/src/fontdb/system.rs
- crates/fonts/src/fontdb/names.rs

* Apply review fixes

- crates/fonts/src/fontdb/system.rs

* address review: cap flattened edges in has_serifs

* address review: check alias blankness before capping

---------

Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
This commit is contained in:
Dylan M. Taylorandaiongg authored and GitHub committed 2026-11-12 06:27:53 -05:00
1 parent 665fe2a8e4
commit 568f25fa9f
4 files changed
+150 -10

No files matched your search

+9
View File
@@ -10,8 +10,17 @@ pub(super) struct SysFallback {
pub(super) misses: std::collections::HashSet<char>,
/// CJK chain families already looked up in the catalog (loaded, or not installed).
pub(super) chain_tried: std::collections::HashSet<&'static str>,
/// The code points each installed file covers (as ranges), by path, valid for the file's
/// stamp: one character after another is looked up without reading every file again.
pub(super) coverage: std::collections::HashMap<std::path::PathBuf, (super::system::FileStamp, std::sync::Arc<Vec<(u32, u32)>>)>,
/// Ranges held in `coverage` (it stops growing at [`MAX_COVERAGE_RANGES`]).
pub(super) coverage_ranges: usize,
}
/// Most coverage ranges kept across all files (8 bytes each).
#[cfg(not(target_arch = "wasm32"))]
pub(super) const MAX_COVERAGE_RANGES: usize = 4 * 1024 * 1024;
#[cfg(all(test, target_os = "linux"))]
pub(super) struct FallbackSnapshotHook {
pub(super) reached: std::sync::mpsc::SyncSender<()>,
+38 -2
View File
@@ -3,16 +3,42 @@ use super::face::FontFace;
use super::system::CatalogEntry;
use super::{Arc, MetadataProvider, StringId};
/// Characters kept of one decoded name: a name record can be 64 KB, and a font has many of them,
/// kept for every face (a real family or style name is far shorter).
const MAX_NAME_CHARS: usize = 256;
fn capped(name: impl std::fmt::Display) -> String {
use std::fmt::Write as _;
let mut out = String::new();
// Stops decoding at the cap: the name is never built in full.
let mut room = MAX_NAME_CHARS;
struct Cap<'a>(&'a mut String, &'a mut usize);
impl std::fmt::Write for Cap<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
for c in s.chars() {
if *self.1 == 0 {
return Err(std::fmt::Error);
}
*self.1 -= 1;
self.0.push(c);
}
Ok(())
}
}
let _ = write!(Cap(&mut out, &mut room), "{name}");
out
}
pub(super) fn aliases(font: &skrifa::FontRef<'_>, ids: &[StringId]) -> Vec<String> {
let mut names: Vec<String> =
ids.iter().flat_map(|id| font.localized_strings(*id)).take(256).map(|name| name.to_string()).filter(|name| !name.trim().is_empty()).collect();
ids.iter().flat_map(|id| font.localized_strings(*id)).take(256).filter(|name| !name.chars().all(char::is_whitespace)).map(capped).collect();
names.sort();
names.dedup();
names
}
pub(super) fn name(font: &skrifa::FontRef<'_>, ids: &[StringId]) -> Option<String> {
ids.iter().find_map(|id| font.localized_strings(*id).english_or_first().map(|s| s.to_string()).filter(|s| !s.is_empty()))
ids.iter().find_map(|id| font.localized_strings(*id).english_or_first().map(capped).filter(|s| !s.is_empty()))
}
/// A face's family and style names (the default instance's style for a variable font).
@@ -116,3 +142,13 @@ pub(super) fn style_italic(style: &str) -> bool {
let s = norm(style);
s.contains("italic") || s.contains("oblique") || s == "it"
}
#[cfg(test)]
mod tests {
#[test]
fn decoded_names_are_capped() {
assert_eq!(super::capped("Arial"), "Arial");
let long = "é".repeat(10_000);
assert_eq!(super::capped(&long).chars().count(), super::MAX_NAME_CHARS);
}
}
+23 -3
View File
@@ -11,28 +11,48 @@ pub(super) fn has_serifs(face: &FontFace) -> Option<bool> {
if bbox.height().is_nan() || bbox.height() <= 1.0 {
return None;
}
// Flattened segments kept: an "l" has a few dozen; a hostile outline has no such limit of its own.
const MAX_EDGES: usize = 1 << 20;
let mut edges = Vec::new();
let mut over = false;
let mut last = None;
let mut start = None;
kurbo::flatten(&path, 1.0, |el| match el {
// A tolerance of one unit is fine for outlines of normal size; a transform that blows one up
// gets a tolerance in proportion, so the subdivision stays bounded (4096 units stay at 1).
let tol = (bbox.width().max(bbox.height()) / 4096.0).max(1.0);
if !tol.is_finite() {
return None;
}
kurbo::flatten(&path, tol, |el| match el {
kurbo::PathEl::MoveTo(p) => {
last = Some(p);
start = Some(p);
}
kurbo::PathEl::LineTo(p) => {
if let Some(a) = last {
edges.push((a, p));
if edges.len() < MAX_EDGES {
edges.push((a, p));
} else {
over = true;
}
}
last = Some(p);
}
kurbo::PathEl::ClosePath => {
if let (Some(a), Some(b)) = (last, start) {
edges.push((a, b));
if edges.len() < MAX_EDGES {
edges.push((a, b));
} else {
over = true;
}
}
last = start;
}
_ => {}
});
if over {
return None;
}
// The outline is y-down: the foot is near bbox.y1 (the baseline).
let width_at = |y: f64| {
let xs: Vec<f64> = edges
+80 -5
View File
@@ -38,6 +38,60 @@ fn file_stamp(path: &std::path::Path) -> Option<FileStamp> {
std::fs::metadata(path).ok().map(|m| (m.len(), m.modified().ok()))
}
/// The code points any face of a font file maps, as sorted inclusive ranges.
#[cfg(not(target_arch = "wasm32"))]
fn coverage_ranges(data: &[u8]) -> Vec<(u32, u32)> {
// One bit per code point: memory stays fixed however many faces (or named instances of one face) map however much.
let mut bits = vec![0u64; 0x110000 / 64];
let mut last = None;
for (i, _, _, _) in enumerate_faces(data) {
if last.replace(i) == Some(i) {
continue;
}
if let Ok(f) = skrifa::FontRef::from_index(data, i) {
for (cp, _) in f.charmap().mappings() {
if let Some(w) = bits.get_mut((cp / 64) as usize) {
*w |= 1u64 << (cp % 64);
}
}
}
}
let mut out: Vec<(u32, u32)> = Vec::new();
for (wi, &w) in bits.iter().enumerate() {
if w == 0 {
continue;
}
for b in 0..64u32 {
if (w >> b) & 1 == 0 {
continue;
}
let cp = wi as u32 * 64 + b;
match out.last_mut() {
Some(last) if last.1 + 1 == cp => last.1 = cp,
_ => out.push((cp, cp)),
}
}
}
out
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod coverage_tests {
#[test]
fn coverage_ranges_match_the_charmap() {
let data = include_bytes!("../../../../assets/fonts/SourceSans3-Regular.ttf");
let ranges = super::coverage_ranges(data);
let f = skrifa::FontRef::new(data).unwrap();
use skrifa::MetadataProvider as _;
let in_ranges = |c: char| ranges.iter().any(|&(lo, hi)| (lo..=hi).contains(&u32::from(c)));
assert!(in_ranges('a') && !in_ranges('\u{10FFFE}'));
for c in ['a', 'Z', 'é', '\u{4E2D}', '\u{1F600}'] {
assert_eq!(in_ranges(c), f.charmap().map(c).is_some(), "{c:?}");
}
assert!(ranges.windows(2).all(|w| w[0].1 + 1 < w[1].0), "sorted and merged");
}
}
/// What [`FontDb::reload_fonts`] did: the faces cataloged by the rescan, and the installed font
/// files that changed or were removed since they were read (their faces are read again, from the
/// new file, the next time they are looked up).
@@ -272,6 +326,8 @@ impl FontDb {
sys.generation = sys.generation.wrapping_add(1);
sys.misses.clear();
sys.chain_tried.clear();
sys.coverage.clear();
sys.coverage_ranges = 0;
count
}
@@ -564,13 +620,32 @@ impl FontDb {
}
}
for p in paths {
if std::fs::metadata(&p).map(|m| m.len() > 40 << 20).unwrap_or(true) {
let Some(stamp) = file_stamp(&p).filter(|s| s.0 <= 40 << 20) else { continue };
let cached =
self.sys.lock().unwrap_or_else(PoisonError::into_inner).coverage.get(&p).filter(|(s, _)| *s == stamp).map(|(_, r)| r.clone());
let mut data = None;
let ranges = match cached {
Some(r) => r,
None => {
let Some(d) = self.read_installed(&p) else { continue };
let r = Arc::new(coverage_ranges(&d));
let mut sys = self.sys.lock().unwrap_or_else(PoisonError::into_inner);
if sys.coverage_ranges + r.len() <= super::fallback::MAX_COVERAGE_RANGES {
sys.coverage_ranges += r.len();
if let Some((_, old)) = sys.coverage.insert(p.clone(), (stamp, r.clone())) {
sys.coverage_ranges -= old.len();
}
}
data = Some(d);
r
}
};
let cp = u32::from(c);
if !ranges.iter().any(|&(lo, hi)| (lo..=hi).contains(&cp)) {
continue;
}
let Some(data) = self.read_installed(&p) else { continue };
let hit =
enumerate_faces(&data).iter().any(|(i, _, _, _)| skrifa::FontRef::from_index(&data, *i).is_ok_and(|f| f.charmap().map(c).is_some()));
if hit && self.add_faces(data, FontSource::Installed(p)) > 0 && covered(self) {
let Some(data) = data.or_else(|| self.read_installed(&p)) else { continue };
if self.add_faces(data, FontSource::Installed(p)) > 0 && covered(self) {
return true;
}
}