mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-22 19:08:29 +00:00
Merge PR #2594: M7: write a full font program compressed and find it again on any page
This commit is contained in:
commit
62f3375297
1 file changed
+39
-7
@@ -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<Item = Written> + '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");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user