From 517aa62946310f9565d4b24523dee1bf41ad2218 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 4 Nov 2026 00:17:38 +0000 Subject: [PATCH] address review: cap the triangles a ScriptUI fill produces --- crates/engine/src/scriptui.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/scriptui.rs b/crates/engine/src/scriptui.rs index 5ced843b3..f4889b00d 100644 --- a/crates/engine/src/scriptui.rs +++ b/crates/engine/src/scriptui.rs @@ -199,6 +199,9 @@ impl ImageRef { pub const MAX_FILL_EDGES: usize = 2048; /// Most horizontal bands a ScriptUI fill is cut into (vertices and edge crossings). pub const MAX_FILL_BANDS: usize = 8192; +/// Most triangles a ScriptUI fill produces (edges × bands can each pass their limit and still +/// multiply into millions); a fill past it paints nothing. +pub const MAX_FILL_TRIANGLES: usize = 1 << 19; impl PathSeg { /// The subpaths of `path` as polylines (ellipses flattened to `n` segments), each with @@ -254,7 +257,8 @@ impl PathSeg { /// convex-only). Exact trapezoid decomposition: the plane is cut into horizontal bands at /// every vertex and edge crossing; inside a band no edges cross, so each inside span is a /// trapezoid (two triangles). A path of more than [`MAX_FILL_EDGES`] edges, or cut into more - /// than [`MAX_FILL_BANDS`] bands, fills nothing: edges are compared pairwise and every band + /// than [`MAX_FILL_BANDS`] bands, or that would make more than [`MAX_FILL_TRIANGLES`] + /// triangles, fills nothing: edges are compared pairwise and every band /// looks at every edge, on the UI thread, for geometry a script chooses. pub fn fill_triangles(path: &[PathSeg], n: usize, even_odd: bool) -> Vec<[[f64; 2]; 3]> { // Non-horizontal edges, top to bottom, with their winding direction. @@ -310,6 +314,9 @@ impl PathSeg { out.push([[l.1, ya], [r.2, yb], [l.2, yb]]); } } + if out.len() > MAX_FILL_TRIANGLES { + return vec![]; + } } out } @@ -956,5 +963,8 @@ mod tests { }) .collect(); assert!(PathSeg::fill_triangles(&poly(&star), 48, false).is_empty()); + // Each limit holds (2,048 edges, 2,048 bands) but together they make 2 million triangles. + let rects: Vec = (0..1024).map(|i| PathSeg::R { x: 2.0 * i as f64, y: i as f64, w: 1.0, h: 2048.0 }).collect(); + assert!(PathSeg::fill_triangles(&rects, 48, false).is_empty()); } }