diff --git a/crates/metafile/src/cliprect.rs b/crates/metafile/src/cliprect.rs
index d85d2a167..ef6bb7c88 100644
--- a/crates/metafile/src/cliprect.rs
+++ b/crates/metafile/src/cliprect.rs
@@ -15,6 +15,10 @@ const CURVE_STEPS: usize = 16;
/// it is dropped, as a single polygon over it already was.
const MAX_CLIP_WORK: usize = 1_000_000;
+/// The same allowance for intersecting two clip regions, a budget of its own: two convex
+/// 1,000-gons cost about 1.5 million tests once the cut polygon has grown.
+pub(crate) const MAX_AREA_CLIP_WORK: usize = 4 * MAX_CLIP_WORK;
+
/// The bounding box `[l, t, r, b]` of the points in `ops` (control points included).
pub(crate) fn ops_bbox(ops: &[Op]) -> Option<[f64; 4]> {
let mut b: Option<[f64; 4]> = None;
@@ -102,15 +106,11 @@ fn is_rect(clip: &[P]) -> bool {
pts_bbox(clip).is_some_and(|b| clip.len() == 4 && clip.iter().all(|p| (p.0 == b[0] || p.0 == b[2]) && (p.1 == b[1] || p.1 == b[3])))
}
-/// Sutherland–Hodgman: `poly` cut by convex `clip`.
-pub(crate) fn clip_polygon(poly: &[P], clip: &[P]) -> Vec
{
- clip_polygon_within(poly, clip, &mut { MAX_CLIP_WORK }).unwrap_or_default()
-}
-
-/// [`clip_polygon`] charging each point visited against each clip edge to `budget`: the polygon
+/// Sutherland–Hodgman: `poly` cut by convex `clip`, charging each point visited against each clip
+/// edge to `budget`: the polygon
/// can gain a vertex at every edge, so its cost isn't known from its input size alone. `None`
/// once the budget is spent.
-fn clip_polygon_within(poly: &[P], clip: &[P], budget: &mut usize) -> Option> {
+pub(crate) fn clip_polygon_within(poly: &[P], clip: &[P], budget: &mut usize) -> Option> {
let mut poly = poly.to_vec();
for i in 0..clip.len() {
// The work actually done: this edge visits the polygon as it now stands.
@@ -168,18 +168,17 @@ pub(crate) fn clip_raw(raw: Raw, clip: &[P]) -> Vec {
return Vec::new();
}
let subs = flatten(&ops);
- if too_much_work(&subs, clip, usize::from(fill.is_some()) + usize::from(stroke.is_some())) {
- return Vec::new();
- }
+ // One allowance for the fill and the stroke together; the item goes once it is spent.
+ let mut budget = MAX_CLIP_WORK;
let mut out = Vec::new();
if fill.is_some() {
- let Some(f) = clip_fill(&subs, clip) else { return Vec::new() };
+ let Some(f) = clip_fill(&subs, clip, &mut budget) else { return Vec::new() };
if !f.is_empty() {
out.push(Raw::Path { ops: f, fill, stroke: None, even_odd, region: region.clone() });
}
}
if stroke.is_some() {
- let s = clip_stroke(&subs, clip);
+ let Some(s) = clip_stroke(&subs, clip, &mut budget) else { return Vec::new() };
if !s.is_empty() {
out.push(Raw::Path { ops: s, fill: None, stroke, even_odd, region });
}
@@ -195,10 +194,7 @@ pub(crate) fn clip_raw(raw: Raw, clip: &[P]) -> Vec {
return Vec::new();
}
let subs = flatten(&ops);
- if too_much_work(&subs, clip, 1) {
- return Vec::new();
- }
- let f = clip_fill(&subs, clip).unwrap_or_default();
+ let f = clip_fill(&subs, clip, &mut { MAX_CLIP_WORK }).unwrap_or_default();
if f.is_empty() { Vec::new() } else { vec![Raw::Pattern { ops: f, even_odd, tile, region }] }
}
Raw::Bitmap { rect, img, region } => crop(rect, img, clip).map(|(rect, img)| vec![Raw::Bitmap { rect, img, region }]).unwrap_or_default(),
@@ -247,21 +243,12 @@ pub(crate) fn flatten(ops: &[Op]) -> Vec<(Vec
, bool)> {
subs
}
-/// Whether cutting every subpath of one item by `clip` would exceed [`MAX_CLIP_WORK`]; the
-/// per-polygon check alone lets many small subpaths each spend the whole allowance. `passes` is
-/// how many clipping passes the item gets (fill and stroke each cut every subpath), so one item
-/// shares a single allowance between them.
-fn too_much_work(subs: &[(Vec
, bool)], clip: &[P], passes: usize) -> bool {
- subs.iter().map(|s| s.0.len() + 1).sum::().saturating_mul(clip.len()).saturating_mul(passes) > MAX_CLIP_WORK
-}
-
-/// The clipped fill of every subpath, its clipping work shared by all of them: `None` (the whole
-/// item goes) once [`MAX_CLIP_WORK`] is spent.
-fn clip_fill(subs: &[(Vec
, bool)], clip: &[P]) -> Option> {
+/// The clipped fill of every subpath, its clipping work charged to `budget` with that of the
+/// stroke: `None` (the whole item goes) once it is spent.
+fn clip_fill(subs: &[(Vec
, bool)], clip: &[P], budget: &mut usize) -> Option> {
let mut ops = Vec::new();
- let mut budget = MAX_CLIP_WORK;
for (pts, _) in subs {
- let poly = clip_polygon_within(pts, clip, &mut budget)?;
+ let poly = clip_polygon_within(pts, clip, budget)?;
if poly.len() >= 3 {
ops.push(Op::M(poly[0]));
ops.extend(poly[1..].iter().map(|p| Op::L(*p)));
@@ -271,7 +258,8 @@ fn clip_fill(subs: &[(Vec