From 529cbf5f2845aa28a2d47d5b7aff4e85c9cd64e5 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 14:52:32 +0000 Subject: [PATCH] address review: charge the pairwise boundary check only for areas --- crates/measure/src/lib.rs | 7 ++++--- crates/measure/src/tests.rs | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/measure/src/lib.rs b/crates/measure/src/lib.rs index 9b2cd2303f..437d2a2010 100644 --- a/crates/measure/src/lib.rs +++ b/crates/measure/src/lib.rs @@ -520,11 +520,12 @@ pub fn list(doc: &Document) -> Listing { Some(b"PolygonDimension") => Kind::Area, _ => continue, }; - // The boundary check compares every pair of edges: charge it before reading the - // vertices, so a listing stops at its work bound rather than at a stall. + // An area's boundary check compares every pair of edges: charge it before reading the + // vertices, so a listing stops at its work bound rather than at a stall. Other kinds + // read their vertices once. let vertices = d.get(if kind == Kind::Distance { b"L".as_slice() } else { b"Vertices" }).map(|v| doc.resolve(v)); let n = vertices.as_deref().and_then(Object::as_array).map_or(0, |a| a.len() / 2).min(MAX_POINTS); - work = work.saturating_add(n.saturating_mul(n)); + work = work.saturating_add(if kind == Kind::Area { n.saturating_mul(n) } else { n.max(1) }); if work > MAX_LISTED_WORK { out.truncated = true; return out; diff --git a/crates/measure/src/tests.rs b/crates/measure/src/tests.rs index 2faa2a6fc3..a90c7b9aa4 100644 --- a/crates/measure/src/tests.rs +++ b/crates/measure/src/tests.rs @@ -511,3 +511,27 @@ fn listing_reads_a_shared_scale_once_and_bounds_boundary_checks() { assert!((5..areas).contains(&l.measurements.len()), "{} listed", l.measurements.len()); assert!(l.unsupported.is_empty(), "{:?}", l.unsupported); } + +/// Perimeters and distances read their vertices once: only areas pay for the pairwise boundary +/// check, so many maximal perimeters list in full. +#[test] +fn perimeters_are_charged_linear_work() { + let measure = "<< /Type /Measure /Subtype /RL /R (1 pt = 1 pt) /X [<< /Type /NumberFormat /U (pt) /C 1 /D 100 >>] /D [<< /Type /NumberFormat /U (pt) /C 1 /D 100 >>] /A [<< /Type /NumberFormat /U (pt) /C 1 /D 100 >>] >>"; + let verts: Vec = (0..MAX_POINTS).map(|i| format!("{} {}", 10 + i % 500, 10 + i / 500 * 3 + (i % 2))).collect(); + let verts = verts.join(" "); + let lines = 40; + let annots: Vec = (0..lines).map(|i| format!("{} 0 R", 6 + i)).collect(); + let mut pdf = Pdf::new() + .obj("<< /Type /Catalog /Pages 2 0 R >>") + .obj("<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 612 792] >>") + .obj(format!("<< /Type /Page /Parent 2 0 R /Contents 4 0 R /Annots [{}] >>", annots.join(" "))) + .stream("", "") + .obj(measure); + for _ in 0..lines { + pdf = pdf.obj(format!("<< /Type /Annot /Subtype /PolyLine /IT /PolyLineDimension /Rect [0 0 612 792] /Vertices [{verts}] /Measure 5 0 R >>")); + } + let d = Document::open(Arc::new(pdf.build())).unwrap(); + let l = list(&d); + assert!(!l.truncated, "{} listed", l.measurements.len()); + assert_eq!(l.measurements.len() + l.unsupported.len(), lines, "{:?}", l.unsupported.first()); +}