diff --git a/crates/fonts/src/embedding.rs b/crates/fonts/src/embedding.rs index 84801d5fd..a5bb011e0 100644 --- a/crates/fonts/src/embedding.rs +++ b/crates/fonts/src/embedding.rs @@ -195,7 +195,7 @@ pub fn embed_mapping( // page; source subset programs stay untouched. let existing = written(doc, fonts).find(|w| w.has_program(doc, bytes)).map(|w| w.program).or_else(|| { doc.scan_objects().take(8192).find_map(|(r, o)| match o.as_ref() { - Object::Stream(s) if s.dict.get(b"PCFullFont").is_some() && s.raw.as_ref() == bytes => Some(Object::Ref(r)), + Object::Stream(s) if is_program(doc, s, bytes) => Some(Object::Ref(r)), _ => None, }) }); @@ -206,7 +206,7 @@ pub fn embed_mapping( d.set(b"Subtype".to_vec(), Object::name("OpenType")); } d.set(b"PCFullFont".to_vec(), Object::Bool(true)); - Object::Ref(doc.add(Object::Stream(Stream::from_raw(d, bytes.to_vec())))) + Object::Ref(doc.add(Object::Stream(Stream::flate(d, bytes)))) }); descriptor.set(if cff { b"FontFile3".to_vec() } else { b"FontFile2".to_vec() }, program); let desc = Object::Ref(doc.add(Object::Dict(descriptor))); @@ -264,15 +264,20 @@ struct Written { } impl Written { - /// Whether its program is `bytes`. The length it was written with is compared before decoding. + /// Whether its program is `bytes`. fn has_program(&self, doc: &Document, bytes: &[u8]) -> bool { - let Object::Stream(s) = doc.resolve(&self.program).as_ref().clone() else { return false }; - s.dict.get(b"PCFullFont").is_some() - && s.dict.get(b"Length1").and_then(|l| doc.resolve(l).as_int()) == i64::try_from(bytes.len()).ok() - && s.decoded_within(17 << 20).is_ok_and(|data| data == bytes) + matches!(doc.resolve(&self.program).as_ref(), Object::Stream(s) if is_program(doc, s, bytes)) } } +/// Whether `s` is a full program written for `bytes`, compressed or not. The length it was +/// written with is compared before decoding. +fn is_program(doc: &Document, s: &Stream, bytes: &[u8]) -> bool { + s.dict.get(b"PCFullFont").is_some() + && s.dict.get(b"Length1").and_then(|l| doc.resolve(l).as_int()) == i64::try_from(bytes.len()).ok() + && s.decoded_within(17 << 20).is_ok_and(|data| data == bytes) +} + /// The `PCFull` resources in `fonts`, in order. fn written<'a>(doc: &'a Document, fonts: &'a Dict) -> impl Iterator + 'a { fonts.iter().filter(|(key, _)| key.starts_with(b"PCFull")).take(256).filter_map(move |(key, value)| { @@ -379,4 +384,31 @@ mod tests { assert!(!fonts.contains(medium.as_bytes()), "{medium} is new"); assert_eq!(programs(&doc), 2); } + + #[test] + fn the_full_program_is_written_compressed_and_another_page_reuses_it() { + let bytes = include_bytes!("../../../assets/fonts/Inter-Regular.ttf"); + let mut doc = Document::new_empty(); + let mut first = Dict::new(); + embed(&mut doc, &mut first, bytes, "Inter").unwrap(); + let program = || { + doc.scan_objects() + .find_map(|(_, o)| match o.as_ref() { + Object::Stream(s) if s.dict.get(b"PCFullFont").is_some() => Some(s.clone()), + _ => None, + }) + .unwrap() + }; + let stream = program(); + assert_eq!(stream.dict.name(b"Filter"), Some(b"FlateDecode".as_slice())); + assert!(stream.raw.len() < bytes.len(), "{} of {} bytes", stream.raw.len(), bytes.len()); + assert_eq!(stream.decoded().unwrap(), bytes); + // Another page's resources don't have it, so it is found among the document's objects. + let objects = doc.scan_objects().count(); + let mut second = Dict::new(); + embed(&mut doc, &mut second, bytes, "Inter").unwrap(); + let programs = doc.scan_objects().filter(|(_, o)| matches!(o.as_ref(), Object::Stream(s) if s.dict.get(b"PCFullFont").is_some())).count(); + assert_eq!(programs, 1, "one program for both pages"); + assert!(doc.scan_objects().count() > objects, "the second page gets its own resource"); + } }