Parse duplicate PDF dictionary keys in linear time

This commit is contained in:
skylar taylor-barrick committed 2026-10-12 23:22:05 +02:00
1 parent 85ef8b92ac
commit c22935270b
2 files changed
+22 -5

No files matched your search

+10 -5
View File
@@ -231,12 +231,16 @@ impl<'a> Lexer<'a> {
}
fn dict_body(&mut self, depth: usize) -> Option<Dict> {
let mut dict: Dict = Vec::new();
// A dictionary's last occurrence of a key wins, including its position. Scanning
// all earlier keys for each insertion makes a small hostile dictionary quadratic.
// Keep bounded slots while reading, then compact them once in final occurrence order.
let mut positions = HashMap::new();
let mut items = Vec::new();
loop {
self.skip_ws();
if self.rest().starts_with(b">>") {
self.pos += 2;
return Some(dict);
return Some(items.into_iter().flatten().collect());
}
if self.peek()? != b'/' {
return None;
@@ -244,9 +248,10 @@ impl<'a> Lexer<'a> {
self.pos += 1;
let key = self.name()?;
let value = self.object(depth + 1)?;
// A repeated key: the last one counts.
dict.retain(|(k, _)| *k != key);
dict.push((key, value));
if let Some(previous) = positions.insert(key.clone(), items.len()) {
*items.get_mut(previous)? = None;
}
items.push(Some((key, value)));
}
}
+12
View File
@@ -224,3 +224,15 @@ fn separate_cached_object_streams_cannot_exceed_the_job_budget() {
pdf.extend_from_slice(format!("\nendstream\nendobj\nstartxref\n{xref}\n%%EOF\n").as_bytes());
assert!(read(&pdf).is_err(), "three small compressed streams must not cache 36 MiB");
}
#[test]
fn duplicate_dictionary_keys_keep_the_last_value_and_last_position() {
let dict = Lexer::new(b"<< /A 1 /B 2 /A 3 /C 4 /B 5 >>", 0).object(0).unwrap();
let dict = dict.dict().unwrap();
assert_eq!(
dict.iter().map(|(key, value)| (key.as_slice(), value.int().unwrap())).collect::<Vec<_>>(),
[(b"A".as_slice(), 3), (b"C".as_slice(), 4), (b"B".as_slice(), 5)]
);
let escaped = Lexer::new(b"<< /A#42 1 /C 2 /AB 3 >>", 0).object(0).unwrap();
assert_eq!(escaped.dict().unwrap(), &vec![(b"C".to_vec(), Obj::Num(b"2".to_vec())), (b"AB".to_vec(), Obj::Num(b"3".to_vec()))]);
}