fix(text): load at most 64 faces from a font collection given as bytes

A collection header names its faces in four bytes each, so a small upload
could declare any number of faces over the same tables; every one was
parsed and kept its own character map under the font database's lock. The
largest real collections hold a few dozen faces; 64 are loaded.
This commit is contained in:
Dylan M. Taylor
2026-11-03 04:29:07 +00:00
parent 02c38a6d95
commit bd28a9c19e
2 changed files with 19 additions and 1 deletions
+7 -1
View File
@@ -640,6 +640,9 @@ struct FallbackLists {
/// The most styles whose fallback faces are kept (a file naming more starts the list again).
const MAX_FALLBACK_STYLES: usize = 64;
/// Most faces loaded from one font collection given as bytes (the largest real collections,
/// the CJK ones, hold a few dozen).
const MAX_COLLECTION_FACES: u32 = 64;
/// [`FontDb::key`]'s and [`FontDb::resolve`]'s answers for the families known in one generation.
#[derive(Default)]
@@ -711,7 +714,10 @@ impl FontDb {
pub fn add_bytes(&mut self, bytes: Vec<u8>) -> Vec<Arc<Face>> {
let shared = Arc::new(bytes);
let count = match FileRef::new(shared.as_slice()) {
Ok(FileRef::Collection(c)) => c.len(),
// A collection's header may name any number of faces, each a few bytes pointing at
// the same tables, and every face keeps its own character map: at most
// [`MAX_COLLECTION_FACES`] of them are loaded.
Ok(FileRef::Collection(c)) => c.len().min(MAX_COLLECTION_FACES),
Ok(FileRef::Font(_)) => 1,
Err(_) => 0,
};
+12
View File
@@ -595,3 +595,15 @@ fn system_font_dirs_are_walked_once_each() {
let unique: std::collections::HashSet<_> = dirs.iter().collect();
assert_eq!(unique.len(), dirs.len(), "{dirs:?}");
}
#[test]
fn a_collection_header_naming_billions_of_faces_loads_a_bounded_few() {
// Four bytes per face offset: a header can name far more faces than the data holds, and each
// one would be parsed (and keep a character map) in turn.
let mut bytes = synthetic_font_collection(32);
bytes[8..12].copy_from_slice(&u32::MAX.to_be_bytes());
let start = std::time::Instant::now();
let faces = FontDb::default().add_bytes(bytes);
assert!(faces.len() <= 64, "{}", faces.len());
assert!(start.elapsed() < std::time::Duration::from_secs(5), "{:?}", start.elapsed());
}