From 0e64aca557462bb6507f6a2e8e2a50a86122f8db Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 20:03:22 +0000 Subject: [PATCH] Bound work: flattening of huge curves, print layout past the page limit, script-check file names --- crates/brush/src/track.rs | 3 ++- crates/brush/src/warp.rs | 2 +- crates/geom/src/lib.rs | 10 +++++++++- crates/metafile/src/wmf.rs | 2 +- crates/pdf/src/print/layout.rs | 11 +++++++++++ crates/pdf/src/tests/print.rs | 8 ++++++++ crates/scriptcheck/src/report.rs | 26 ++++++++++++++++++++++---- 7 files changed, 54 insertions(+), 8 deletions(-) diff --git a/crates/brush/src/track.rs b/crates/brush/src/track.rs index 7d8799186..6c6524617 100644 --- a/crates/brush/src/track.rs +++ b/crates/brush/src/track.rs @@ -236,7 +236,8 @@ pub fn tracks(bp: &BezPath, tol: f64) -> Vec { return out; } let mut els = vec![]; - kurbo::flatten(bp.iter(), tol.max(1e-4), |el| { + // Guarded: kurbo works out every subdivision of a curve even when the callback drops them. + vectorcraft_geom::flatten(bp.iter(), tol.max(1e-4), |el| { if els.len() < MAX_FLATTENED { els.push(el); } diff --git a/crates/brush/src/warp.rs b/crates/brush/src/warp.rs index ae0c170f3..c52f7f630 100644 --- a/crates/brush/src/warp.rs +++ b/crates/brush/src/warp.rs @@ -103,7 +103,7 @@ fn densified(sp: &SubPath, flat_tol: f64, max_du: f64) -> Option> { let mut bp = BezPath::new(); sp.to_bezpath_into(&mut bp); let mut pts = vec![]; - kurbo::flatten(bp.iter(), flat_tol.max(1e-4), |e| { + vectorcraft_geom::flatten(bp.iter(), flat_tol.max(1e-4), |e| { if let PathEl::MoveTo(q) | PathEl::LineTo(q) = e { pts.push(q); } diff --git a/crates/geom/src/lib.rs b/crates/geom/src/lib.rs index 118d3f0b9..b58a31ad3 100644 --- a/crates/geom/src/lib.rs +++ b/crates/geom/src/lib.rs @@ -29,6 +29,11 @@ pub const EPS: f64 = 1e-9; /// Most line segments [`flatten`] makes of one curve, whatever its size and the tolerance. pub const MAX_FLATTEN_PER_CURVE: f64 = 4096.0; +/// A curve whose control polygon is longer than this is drawn as a line by [`flatten`]: kurbo +/// squares and cubes coordinates when it sizes its work, and with coordinates this large that +/// overflows to infinity and it reserves a buffer no machine has. (Far beyond any drawing.) +pub const MAX_FLATTEN_LENGTH: f64 = 1e60; + /// [`kurbo::flatten`], each curve's tolerance raised so that it makes at most about /// [`MAX_FLATTEN_PER_CURVE`] segments. kurbo sizes its work and a buffer from the curve's /// coordinates before producing anything, so a small curve with far-out handles (from a file, or @@ -60,7 +65,7 @@ pub fn flatten(path: impl IntoIterator, tolerance: f64, mut callb }; last = Some(to); let (Some(from), Some(length)) = (from, length) else { continue }; - if !length.is_finite() { + if length.is_nan() || length > MAX_FLATTEN_LENGTH { callback(PathEl::LineTo(to)); continue; } @@ -198,6 +203,9 @@ mod tests { assert_eq!(ours, theirs); let nan = [PathEl::MoveTo(Point::ZERO), PathEl::QuadTo(Point::new(f64::NAN, 0.0), Point::new(1.0, 1.0))]; assert_eq!(count(&nan, 0.25), 2); + // Finite coordinates whose squares overflow are a line to the end too. + let huge = [PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::new(1e200, 0.0), Point::new(-1e200, 1e200), Point::new(1.0, 0.0))]; + assert_eq!(count(&huge, 0.25), 2); } #[test] diff --git a/crates/metafile/src/wmf.rs b/crates/metafile/src/wmf.rs index 0e4b82e08..33a1b081d 100644 --- a/crates/metafile/src/wmf.rs +++ b/crates/metafile/src/wmf.rs @@ -75,7 +75,7 @@ pub(crate) fn checksum(header: &[u8]) -> u16 { fn flatten(bp: &BezPath, tol: f64) -> Vec<(Vec, bool)> { let mut out: Vec<(Vec, bool)> = vec![]; let mut cur: Vec = vec![]; - kurbo::flatten(bp.iter(), tol, |el| match el { + vectorcraft_geom::flatten(bp.iter(), tol, |el| match el { PathEl::MoveTo(p) => { if cur.len() > 1 { out.push((std::mem::take(&mut cur), false)); diff --git a/crates/pdf/src/print/layout.rs b/crates/pdf/src/print/layout.rs index d3a54f896..21462e6b9 100644 --- a/crates/pdf/src/print/layout.rs +++ b/crates/pdf/src/print/layout.rs @@ -165,6 +165,10 @@ fn turns(set: &PrintSettings, (w, h): (f64, f64), flipped: bool) -> (Affine, (f6 (v, (w, h)) } +fn too_many_pages() -> PdfError { + PdfError::BadSetting(format!("the job would print more than {} pages", super::MAX_PAGES)) +} + /// The pages of `doc` (whose layers are those that print) as `set` lays them out, the tiles of /// each artboard when tiling, and warnings (art that doesn't fit the imageable area). pub(crate) fn layout(doc: &Document, set: &PrintSettings, warnings: &mut Vec) -> Result<(Vec, Vec), PdfError> { @@ -174,6 +178,10 @@ pub(crate) fn layout(doc: &Document, set: &PrintSettings, warnings: &mut Vec super::MAX_PAGES { + return Err(too_many_pages()); + } let what = artboard.map_or_else(|| "The art".to_string(), |i| format!("Artboard {}", i + 1)); if !(r.width() > 0.0 && r.height() > 0.0) { warnings.push(format!("{what} has no area and was left out")); @@ -297,6 +305,9 @@ pub(crate) fn layout(doc: &Document, set: &PrintSettings, warnings: &mut Vec super::MAX_PAGES { + return Err(too_many_pages()); + } for &t in &pick { let w = window(t); let view = turn * Affine::translate((at.0 - w.x0, at.1 - w.y0)); diff --git a/crates/pdf/src/tests/print.rs b/crates/pdf/src/tests/print.rs index 019beddd7..a6b4d2784 100644 --- a/crates/pdf/src/tests/print.rs +++ b/crates/pdf/src/tests/print.rs @@ -239,6 +239,14 @@ fn tiles_cover_the_artboard_with_overlap() { assert!(preview(&d, &settings(json!({"scaling": "tileFull", "overlap": 400}))).is_err(), "overlap of half a tile"); } +#[test] +fn a_job_of_too_many_artboards_is_refused_while_laying_out() { + let mut d = doc(100.0, 100.0, &[]); + d.artboards.extend((2..=MAX_PAGES as u32 + 2).map(|i| artboard(i, 0.0))); + let e = preview(&d, &settings(json!({}))).unwrap_err().to_string(); + assert!(e.contains("more than 2000 pages"), "{e}"); +} + #[test] fn artboard_range_skip_blank_and_ignore() { let mut d = diff --git a/crates/scriptcheck/src/report.rs b/crates/scriptcheck/src/report.rs index 1c55ce1b4..b21feb20e 100644 --- a/crates/scriptcheck/src/report.rs +++ b/crates/scriptcheck/src/report.rs @@ -370,14 +370,14 @@ impl<'l> Acc<'l> { pub fn problem(&mut self, code: &'static str, line: Option, file: Option<&str>, detail: Option<&str>) { if self.room(self.problems.len()) { let detail = detail.map(|d| self.echo(d)); - self.problems.push(Problem { code, line, file: file.map(str::to_string), detail }); + self.problems.push(Problem { code, line, file: file.map(|f| self.echo(f)), detail }); } } pub fn directive(&mut self, name: &'static str, value: &str, line: usize, form: &'static str, file: Option<&str>) { if self.room(self.directives.len()) { let value = self.echo(value); - self.directives.push(Directive { name, value, line, form, file: file.map(str::to_string) }); + self.directives.push(Directive { name, value, line, form, file: file.map(|f| self.echo(f)) }); } } @@ -394,14 +394,14 @@ impl<'l> Acc<'l> { version: part(t.version), locale: part(t.locale), line, - file: file.map(str::to_string), + file: file.map(|f| self.echo(f)), }; self.targets.push(target); } pub fn include(&mut self, value: &str, line: usize, file: Option<&str>, status: IncludeStatus, path: Option<&str>) { if self.room(self.includes.len()) { - let include = Include { value: self.echo(value), line, file: file.map(str::to_string), status, path: path.map(|p| self.echo(p)) }; + let include = Include { value: self.echo(value), line, file: file.map(|f| self.echo(f)), status, path: path.map(|p| self.echo(p)) }; self.includes.push(include); } } @@ -461,3 +461,21 @@ fn echo_cow(s: &str, max: usize) -> Cow<'_, str> { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_file_of_a_row_is_cut_like_the_rest_of_the_echo() { + let limits = Limits::default(); + let mut acc = Acc::new(&limits); + let long = "d/".repeat(5000); + acc.problem("x", Some(1), Some(&long), None); + acc.directive("include", "v", 1, "form", Some(&long)); + acc.target("illustrator", 1, Some(&long)); + for file in [&acc.problems[0].file, &acc.directives[0].file, &acc.targets[0].file] { + assert_eq!(file.as_deref().map(|f| f.chars().count()), Some(limits.echo)); + } + } +}