mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-27 11:37:59 -06:00
M7: write a full font program compressed and find it again on any page
A text edit that needs glyphs the source font's embedding lacks writes the face's full program once. It was written uncompressed, and the document-wide search that lets another page reuse it compared the stream's raw bytes, so it only matched an uncompressed copy. Write the program with Flate, and compare its decoded bytes (after the /Length1 check) both on the page and across the document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
dc41044ba7
commit
8ebe4f7a76
@@ -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