mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 22:41:47 -06:00
fix(measure): read a stream's XObject resources once when snapping
Every Do operator resolved the stream's /XObject resources again, and resolving a direct dictionary copies it, so a large direct XObject dictionary and many short Do operators multiplied the copying. The dictionary is read on the first Do and kept for the rest of the stream.
This commit is contained in:
@@ -250,6 +250,8 @@ impl Walker<'_> {
|
||||
let mut current = None;
|
||||
let mut closures = Vec::new();
|
||||
let mut path = Geometry::default();
|
||||
// Read on the first `Do` and kept: resolving a direct dictionary copies it.
|
||||
let mut xobjects = None;
|
||||
let point = |m: Matrix, x: f64, y: f64| {
|
||||
let (x, y) = m.apply(x, y);
|
||||
[x, y]
|
||||
@@ -384,7 +386,11 @@ impl Walker<'_> {
|
||||
}
|
||||
"Do" => {
|
||||
if let Some(name) = op.name(0) {
|
||||
let xobjects = resources.get(b"XObject").map(|o| self.doc.resolve(o));
|
||||
let xobjects = xobjects.get_or_insert_with(|| {
|
||||
#[cfg(test)]
|
||||
XOBJECT_DICT_READS.with(|n| n.set(n.get() + 1));
|
||||
resources.get(b"XObject").map(|o| self.doc.resolve(o))
|
||||
});
|
||||
if let Some(o) = xobjects.as_ref().and_then(|o| o.as_dict()).and_then(|d| d.get(name)) {
|
||||
let r = o.as_ref();
|
||||
if r.is_some_and(|r| self.seen.contains(&r)) {
|
||||
@@ -420,6 +426,12 @@ impl Walker<'_> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
/// Times a content stream's `/XObject` resources were read.
|
||||
pub(crate) static XOBJECT_DICT_READS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
pub fn geometry(doc: &Document, page_index: usize) -> Result<Geometry> {
|
||||
let p = crate::page(doc, page_index)?;
|
||||
let resources = p.dict.get(b"Resources").map(|o| doc.resolve(o));
|
||||
|
||||
@@ -403,3 +403,14 @@ fn a_scale_ratio_converts_its_page_side_to_points() {
|
||||
assert!(Scale::ratio([0.0, 0.0], [1.0, 0.0], page, PageUnit::Mm, real, "mm", 2).is_err(), "{page} {real}");
|
||||
}
|
||||
}
|
||||
/// A content stream reads its `/XObject` resources once, not again (as a copy of a direct
|
||||
/// dictionary) for every `Do`.
|
||||
#[test]
|
||||
fn xobject_resources_are_read_once_per_stream() {
|
||||
let d = fixture(&"/Missing Do\n".repeat(10_000), "", &[]);
|
||||
snap::XOBJECT_DICT_READS.with(|c| c.set(0));
|
||||
let paths = snap::geometry(&d, 0).unwrap();
|
||||
assert!(paths.segments.is_empty());
|
||||
let reads = snap::XOBJECT_DICT_READS.with(std::cell::Cell::get);
|
||||
assert!(reads <= 1, "{reads} reads of the page's XObject resources");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user