mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 17:40:54 +00:00
Bound work: flattening of huge curves, print layout past the page limit, script-check file names
This commit is contained in:
committed by
bflatastic
parent
079f5389a6
commit
0e64aca557
@@ -236,7 +236,8 @@ pub fn tracks(bp: &BezPath, tol: f64) -> Vec<Track> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ fn densified(sp: &SubPath, flat_tol: f64, max_du: f64) -> Option<Vec<Point>> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<Item = PathEl>, 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]
|
||||
|
||||
@@ -75,7 +75,7 @@ pub(crate) fn checksum(header: &[u8]) -> u16 {
|
||||
fn flatten(bp: &BezPath, tol: f64) -> Vec<(Vec<Point>, bool)> {
|
||||
let mut out: Vec<(Vec<Point>, bool)> = vec![];
|
||||
let mut cur: Vec<Point> = 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));
|
||||
|
||||
@@ -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<String>) -> Result<(Vec<Layout>, Vec<TileGrid>), PdfError> {
|
||||
@@ -174,6 +178,10 @@ pub(crate) fn layout(doc: &Document, set: &PrintSettings, warnings: &mut Vec<Str
|
||||
let origin = set.tile_origin.placed.then_some((set.tile_origin.x, set.tile_origin.y));
|
||||
let (mut pages, mut grids) = (vec![], vec![]);
|
||||
for (artboard, r) in regions(doc, set)? {
|
||||
// The job is refused past MAX_PAGES (copies and inks only add to it): stop making pages here.
|
||||
if pages.len() > 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<Str
|
||||
let (x, y) = (grid.0 + c * step.0, grid.1 + rw * step.1);
|
||||
Rect::new(x, y, x + tile.0, y + tile.1)
|
||||
};
|
||||
if pages.len() + pick.len() > 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));
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -370,14 +370,14 @@ impl<'l> Acc<'l> {
|
||||
pub fn problem(&mut self, code: &'static str, line: Option<usize>, 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user