mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-01 22:19:10 -06:00
M2: review of #1567: read each installed font file once and share it
CodeRabbit: the fallback cache was keyed by (script, weight) and every key read and kept its own copy of the file, so PingFang.ttc (tens of megabytes, a candidate of several scripts and both weights) could be held up to four times. The bytes now come from a process-wide cache keyed by path, remembering files that cannot be used too; the per-script cache keeps a handle and a face index. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
07a6c59b38
commit
5b3b87430b
@@ -86,13 +86,30 @@ pub(crate) fn fallback(query: &FallbackFontQuery) -> Option<(FontData, u32)> {
|
||||
}
|
||||
|
||||
fn read(candidate: &Candidate, probe: char, bold: bool) -> Option<(Arc<Vec<u8>>, u32)> {
|
||||
let meta = std::fs::metadata(&candidate.path).ok()?;
|
||||
if !meta.is_file() || meta.len() > MAX_BYTES {
|
||||
let bytes = file_bytes(&candidate.path)?;
|
||||
let index = pick_face(&bytes, probe, candidate.region, bold)?;
|
||||
Some((bytes, index))
|
||||
}
|
||||
|
||||
/// The bytes of an installed font file, read once for the whole process and shared: a collection
|
||||
/// such as `PingFang.ttc` is tens of megabytes and is the candidate of several scripts and both
|
||||
/// weights, so the per-script cache keeps only a handle and a face index. A file that cannot be
|
||||
/// used is remembered too, so it is not tried again for every script.
|
||||
fn file_bytes(path: &Path) -> Option<Arc<Vec<u8>>> {
|
||||
/// The bytes of each file tried, or `None` for one that cannot be used.
|
||||
type Files = HashMap<PathBuf, Option<Arc<Vec<u8>>>>;
|
||||
static FILES: Mutex<Option<Files>> = Mutex::new(None);
|
||||
let mut files = FILES.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
files.get_or_insert_with(HashMap::new).entry(path.to_path_buf()).or_insert_with(|| read_file(path, MAX_BYTES).map(Arc::new)).clone()
|
||||
}
|
||||
|
||||
/// A regular file of at most `max` bytes, or `None`: a font path is untrusted input.
|
||||
fn read_file(path: &Path, max: u64) -> Option<Vec<u8>> {
|
||||
let meta = std::fs::metadata(path).ok()?;
|
||||
if !meta.is_file() || meta.len() > max {
|
||||
return None;
|
||||
}
|
||||
let bytes = std::fs::read(&candidate.path).ok()?;
|
||||
let index = pick_face(&bytes, probe, candidate.region, bold)?;
|
||||
Some((Arc::new(bytes), index))
|
||||
std::fs::read(path).ok()
|
||||
}
|
||||
|
||||
/// The best face of the file that parses and maps `probe`: the region's face when the file has
|
||||
@@ -199,6 +216,26 @@ mod tests {
|
||||
assert!(read(&dir, '\u{7269}', false).is_none());
|
||||
}
|
||||
|
||||
/// The files are bounded by `max`: a larger one is not read.
|
||||
#[test]
|
||||
fn a_file_over_the_cap_is_not_read() {
|
||||
let inter = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/fonts/Inter-Regular.ttf"));
|
||||
assert!(read_file(inter, MAX_BYTES).is_some());
|
||||
assert!(read_file(inter, 1024).is_none());
|
||||
}
|
||||
|
||||
/// Two faces drawn from one installed file (two scripts, or regular and bold) hold the same
|
||||
/// bytes, not a copy each; a file that can't be used is remembered as unusable.
|
||||
#[test]
|
||||
fn one_installed_file_is_read_once_and_shared() {
|
||||
let inter = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/fonts/Inter-Regular.ttf"));
|
||||
let first = file_bytes(inter).expect("readable");
|
||||
let second = file_bytes(inter).expect("readable");
|
||||
assert!(Arc::ptr_eq(&first, &second), "one allocation for the file");
|
||||
let missing = Path::new("definitely/not/here.ttc");
|
||||
assert!(file_bytes(missing).is_none() && file_bytes(missing).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_latin_face_is_rejected_for_every_script() {
|
||||
let inter = include_bytes!("../../../assets/fonts/Inter-Regular.ttf");
|
||||
|
||||
Reference in New Issue
Block a user