mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-07 12:13:20 +00:00
font-store: index PostScript names shared by many files in linear time (#538)
Index::assemble searched every earlier position listed under a PostScript name before adding the next, so an index that gives one name to many files took time quadratic in their number. Files are visited in order, so a file that lists a name twice can only be the last position pushed: comparing with the end is enough. find_postscript compared each hit's family name with every name collected so far; it now keeps the lowercased names in a set.
This commit is contained in:
@@ -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<Face<'_>, 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<String> = 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);
|
||||
}
|
||||
|
||||
@@ -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<serde_json::Value> = (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();
|
||||
|
||||
Reference in New Issue
Block a user