mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 20:08:42 +00:00
Merge feat/hpgl-export into feat/cut-order
# Conflicts: # docs/mcp.md
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
//! Coordinates are plotter units (1016 to the inch, y up) from the region's bottom-left corner or
|
||||
//! its center, rounded to whole units. Art past ±8,388,607 units (2²³ − 1, the smallest range
|
||||
//! the HP-GL/2 reference asks a device to take for encoded polylines) from the origin is cut off.
|
||||
//! An HP-GL plot with a coordinate outside −32,768 to 32,767 (16-bit numbers, the range older
|
||||
//! devices read) gets a warning; HP-GL/2's encoded polylines take the wider range.
|
||||
//! The pens are drawn in turn (pen 1's paths, then pen 2's), each in paint order or, with
|
||||
//! `sort`, each next path the one that starts nearest where the pen stopped. With `inside_first`,
|
||||
//! a pen draws a path only after every path of its own that lies inside it (a letter's counter
|
||||
@@ -43,6 +45,8 @@ use clip::Region;
|
||||
const PLU_PER_PT: f64 = 1016.0 / 72.0;
|
||||
/// The largest coordinate written, plotter units.
|
||||
pub const MAX_COORD: i64 = (1 << 23) - 1;
|
||||
/// The coordinates an HP-GL plot keeps to without a warning: 16-bit numbers, plotter units.
|
||||
const CLASSIC_RANGE: std::ops::RangeInclusive<i64> = -32_768..=32_767;
|
||||
/// The curve tolerance a path is flattened to by default, points.
|
||||
pub const DEFAULT_TOLERANCE: f64 = 0.1;
|
||||
/// The smallest and largest curve tolerance, points.
|
||||
@@ -72,6 +76,7 @@ const TOO_MANY_POINTS: &str = "the export is too large: the art past its first 1
|
||||
const CLIP_WORK: &str = "clipping masks are too complex to apply in full: the art past that point is drawn whole";
|
||||
const MASK_POINTS: &str = "clipping masks past 10 million points together are left out: the art they clip is drawn unclipped by them";
|
||||
const OUT_OF_RANGE: &str = "art past 8,388,607 plotter units (about 210 m) from the origin is cut off";
|
||||
const PAST_CLASSIC_RANGE: &str = "the plot has coordinates past 32,767 plotter units (about 82 cm) from the origin, which older HP-GL devices may not read: HP-GL/2 takes a wider range";
|
||||
const MANY_COLORS: &str = "the art has more than 255 colors: the colors past those draw with the pen of the nearest color";
|
||||
const UNSORTED: &str = "the plot has too many paths to sort in full: the rest are drawn in paint order";
|
||||
const UNSORTED_INSIDE: &str = "the plot has too many paths to sort in full: the rest are drawn in paint order, each after the paths inside it";
|
||||
@@ -144,9 +149,11 @@ impl Origin {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HpglOptions {
|
||||
pub version: Version,
|
||||
/// How far a flattened curve may lie from the curve, points ([`TOLERANCE_RANGE`]).
|
||||
/// How far a flattened curve may lie from the curve, points ([`TOLERANCE_RANGE`]; a value
|
||||
/// outside it is an error).
|
||||
pub tolerance: f64,
|
||||
/// The pen table: pen 1's color first. Empty: a pen for each color the art draws in.
|
||||
/// The pen table: pen 1's color first, up to [`MAX_PENS`] (more is an error). Empty: a pen for
|
||||
/// each color the art draws in.
|
||||
pub pens: Vec<[u8; 3]>,
|
||||
pub origin: Origin,
|
||||
/// Draw each pen's paths nearest first, instead of in paint order.
|
||||
@@ -196,8 +203,13 @@ pub fn export_regions(doc: &Document, opts: &HpglOptions, regions: &[Rect]) -> R
|
||||
if regions.iter().any(|r| ![r.x0, r.y0, r.x1, r.y1].iter().all(|v| v.is_finite()) || r.width() <= 0.0 || r.height() <= 0.0) {
|
||||
return Err("the export region is empty".into());
|
||||
}
|
||||
let tolerance =
|
||||
if opts.tolerance.is_finite() { opts.tolerance.clamp(*TOLERANCE_RANGE.start(), *TOLERANCE_RANGE.end()) } else { DEFAULT_TOLERANCE };
|
||||
if !TOLERANCE_RANGE.contains(&opts.tolerance) {
|
||||
return Err("the curve tolerance must be from 0.01 to 10 points".into());
|
||||
}
|
||||
if opts.pens.len() > MAX_PENS {
|
||||
return Err("the pen table holds more than 255 pens".into());
|
||||
}
|
||||
let tolerance = opts.tolerance;
|
||||
// Layers the plot leaves out go first: baking can wrap one in art that would be drawn (its
|
||||
// crop marks).
|
||||
let shown = plotted(&doc.layers, false).map(|layers| {
|
||||
@@ -274,6 +286,9 @@ pub fn export_regions(doc: &Document, opts: &HpglOptions, regions: &[Rect]) -> R
|
||||
at = reorder(paths, at, &mut work, opts.sort, opts.inside_first, &mut warnings);
|
||||
}
|
||||
}
|
||||
if opts.version == Version::Hpgl && by_pen.iter().flatten().flatten().any(|(x, y)| !CLASSIC_RANGE.contains(x) || !CLASSIC_RANGE.contains(y)) {
|
||||
warn(&mut warnings, PAST_CLASSIC_RANGE);
|
||||
}
|
||||
let bytes = match opts.version {
|
||||
Version::Hpgl => plain(&by_pen),
|
||||
Version::Hpgl2 => encoded(&by_pen, &pens),
|
||||
|
||||
@@ -441,6 +441,22 @@ fn an_empty_or_zero_length_plot_has_no_moves() {
|
||||
assert!(export(&d, &nan).is_err());
|
||||
}
|
||||
|
||||
/// A tolerance outside its range and a pen table of more than 255 pens are errors, not changed.
|
||||
#[test]
|
||||
fn bad_options_are_errors() {
|
||||
let d = three();
|
||||
for tolerance in [0.0, 0.009, 10.01, f64::NAN, f64::INFINITY] {
|
||||
let o = HpglOptions { tolerance, ..opts(&d) };
|
||||
assert_eq!(export(&d, &o).err().as_deref(), Some("the curve tolerance must be from 0.01 to 10 points"), "{tolerance}");
|
||||
}
|
||||
for tolerance in [0.01, 10.0] {
|
||||
assert!(export(&d, &HpglOptions { tolerance, ..opts(&d) }).is_ok(), "{tolerance}");
|
||||
}
|
||||
let o = HpglOptions { pens: vec![[0, 0, 0]; MAX_PENS + 1], ..opts(&d) };
|
||||
assert_eq!(export(&d, &o).err().as_deref(), Some("the pen table holds more than 255 pens"));
|
||||
assert!(export(&d, &HpglOptions { pens: vec![[0, 0, 0]; MAX_PENS], ..opts(&d) }).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn art_past_the_plotter_range_is_cut_off() {
|
||||
let d = doc_with(|d| vec![stroked(d, line((0.0, 50.0), (1e9, 50.0)), Color::BLACK)]);
|
||||
@@ -454,6 +470,27 @@ fn art_past_the_plotter_range_is_cut_off() {
|
||||
assert_eq!(text(&out), "IN;PU;SP1;PU0,706;PD141,706;PU;SP0;");
|
||||
}
|
||||
|
||||
/// HP-GL past 16-bit coordinates is written as it is, with a warning; HP-GL/2 gets none.
|
||||
#[test]
|
||||
fn hpgl_past_16_bit_coordinates_warns() {
|
||||
let region = Rect::new(-2400.0, 0.0, 2400.0, 100.0);
|
||||
// Past 32,767 from the bottom-left corner, within the range from the center.
|
||||
let d = doc_with(|d| vec![stroked(d, line((-2000.0, 50.0), (2000.0, 50.0)), Color::BLACK)]);
|
||||
let out = plot(&d, &HpglOptions { region, ..HpglOptions::default() });
|
||||
assert_eq!(out.warnings, [PAST_CLASSIC_RANGE]);
|
||||
assert_eq!(text(&out), "IN;PU;SP1;PU5644,706;PD62089,706;PU;SP0;");
|
||||
let out = plot(&d, &HpglOptions { region, origin: Origin::Center, ..HpglOptions::default() });
|
||||
assert!(out.warnings.is_empty(), "{:?}", out.warnings);
|
||||
assert_eq!(text(&out), "IN;PU;SP1;PU-28222,0;PD28222,0;PU;SP0;");
|
||||
// Past −32,768 and 32,767 from the center.
|
||||
let d = doc_with(|d| vec![stroked(d, line((-2400.0, 50.0), (2400.0, 50.0)), Color::BLACK)]);
|
||||
let out = plot(&d, &HpglOptions { region, origin: Origin::Center, ..HpglOptions::default() });
|
||||
assert_eq!(out.warnings, [PAST_CLASSIC_RANGE]);
|
||||
assert_eq!(text(&out), "IN;PU;SP1;PU-33867,0;PD33867,0;PU;SP0;");
|
||||
let out = plot(&d, &HpglOptions { region, origin: Origin::Center, version: Version::Hpgl2, ..HpglOptions::default() });
|
||||
assert!(out.warnings.is_empty(), "{:?}", out.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_plot_stops_at_its_point_budget() {
|
||||
let d = doc_with(|d| (0..MAX_POINTS / 4).map(|i| stroked(d, rect(1.0, 1.0, 2.0 + (i % 50) as f64, 3.0), Color::BLACK)).collect());
|
||||
|
||||
@@ -22,7 +22,7 @@ pub(super) const OPTIONS: &[FormatOption] = &[
|
||||
name: "hpglVersion",
|
||||
ty: "string",
|
||||
default: "\"hpgl\"",
|
||||
description: "hpgl (IN, SP, PU and PD only) or hpgl2 (also the pens' colors, NP and PC, and each pen's paths as one encoded polyline, PE)",
|
||||
description: "hpgl (IN, SP, PU and PD only; a coordinate outside -32,768 to 32,767 gets a warning) or hpgl2 (also the pens' colors, NP and PC, and each pen's paths as one encoded polyline, PE)",
|
||||
},
|
||||
FormatOption {
|
||||
name: "curveTolerance",
|
||||
|
||||
+5
-3
@@ -6425,10 +6425,12 @@ VectorCraft's own.
|
||||
|
||||
`hpglVersion: "hpgl"` (the default) writes `IN;`, then for each pen `PU;SP<n>;` and each path `PUx,y;PDx,y,…;`, and
|
||||
ends with `PU;SP0;`. `"hpgl2"` also writes the palette (`NP` and a `PC` color for each pen) and draws each pen's
|
||||
paths as one encoded polyline (`PE`, base 64), which takes fewer bytes than the same moves as text. `warnings` names
|
||||
paths as one encoded polyline (`PE`, base 64), which takes fewer bytes than the same moves as text. An `"hpgl"` plot
|
||||
with a coordinate outside −32,768 to 32,767 units (about 82 cm from the origin, the range of 16-bit numbers) is written
|
||||
as it is, with a warning that older devices may not read it; this check is VectorCraft's own. `warnings` names
|
||||
type drawn as outlines, inline graphics in type (left out), placed images, opacity masks, raster effects, stroke
|
||||
looks that are not drawn, art past the plotter's range, colors past 255 pens, and the limits on points, clipping
|
||||
work, symbols, live art, nesting depth and sorting. Sorting and finding which paths lie inside which share one
|
||||
looks that are not drawn, art past the plotter's range, HP-GL coordinates past 16-bit numbers, colors past 255 pens,
|
||||
and the limits on points, clipping work, symbols, live art, nesting depth and sorting. Sorting and finding which paths lie inside which share one
|
||||
work budget for the plot, 500 million box and point comparisons. When sorting runs out of it, the paths not yet
|
||||
drawn keep paint order, each after the paths inside it with `insideFirst`. When it runs out while the writer is
|
||||
finding which paths lie inside which, the paths of that pen and of the later pens keep paint order. Both cases add
|
||||
|
||||
Reference in New Issue
Block a user