diff --git a/crates/font-store/src/index.rs b/crates/font-store/src/index.rs index aad4bc45..bb3073e0 100644 --- a/crates/font-store/src/index.rs +++ b/crates/font-store/src/index.rs @@ -1,7 +1,7 @@ //! Font indexes (aiongg/craft-font-index, format 1.x): the families of every source in one //! [`Index`], and exact lookup. -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; use serde::Deserialize; @@ -519,8 +519,11 @@ impl Index { by_name.entry(fam.family.to_lowercase()).or_default().push(fi); for (xi, file) in fam.files.iter().enumerate() { for ps in &file.postscript { + // Files are visited in order, so a file that lists a name twice can only + // have been the last one pushed: checking the end keeps this linear + // however many files share the name. let hits = by_postscript.entry(ps.clone()).or_default(); - if !hits.contains(&(fi, xi)) { + if hits.last() != Some(&(fi, xi)) { hits.push((fi, xi)); } } @@ -610,9 +613,10 @@ impl Index { pub fn find_postscript(&self, name: &str) -> Result, Unavailable> { let hits = self.by_postscript.get(name).map(Vec::as_slice).unwrap_or_default(); let mut names: Vec<&str> = Vec::new(); + let mut seen: HashSet = HashSet::new(); for &(fi, _) in hits { if let Some(f) = self.families.get(fi) - && !names.iter().any(|n| n.to_lowercase() == f.family.to_lowercase()) + && seen.insert(f.family.to_lowercase()) { names.push(&f.family); } diff --git a/crates/font-store/src/tests.rs b/crates/font-store/src/tests.rs index 44b75b1c..13a44f61 100644 --- a/crates/font-store/src/tests.rs +++ b/crates/font-store/src/tests.rs @@ -206,6 +206,25 @@ fn postscript_lookups_match_exactly() { assert!(index.family("Shared Two").unwrap().face_by_postscript("Shared-Regular").is_some()); } +#[test] +fn many_files_sharing_a_postscript_name_index_in_linear_time() { + let mut v = fixture().index; + let bytes = font("Many", "Regular", 400); + for f in 0..40 { + let files: Vec = (0..2500) + .map(|i| file_entry(&format!("ofl/many{f}/Many-{i}.ttf"), &bytes, "normal", 400, json!([]), &["Many-Regular", "Many-Regular"])) + .collect(); + let family = json!({"family": format!("Many {f}"), "license": "OFL", "license_path": null, "files": files}); + v["families"].as_array_mut().unwrap().push(family); + } + let index = index_of(&v).unwrap(); + let Err(Unavailable::Ambiguous { families, .. }) = index.find_postscript("Many-Regular") else { panic!("not ambiguous") }; + assert_eq!(families.len(), 40); + assert_eq!(families[0], "Many 0"); + let first = index.family("Many 7").unwrap(); + assert_eq!(first.face_by_postscript("Many-Regular").unwrap().file_name(), "Many-0.ttf"); +} + #[test] fn styles_parse_and_urls_pin_the_commit() { let index = index_of(&fixture().index).unwrap();