address review: charge the pairwise boundary check only for areas

This commit is contained in:
Dylan M. Taylor
2026-11-03 14:52:32 +00:00
parent 4c9be5794b
commit 529cbf5f28
2 changed files with 28 additions and 3 deletions
+4 -3
View File
@@ -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;
+24
View File
@@ -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<String> = (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<String> = (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());
}