HP-GL export: an HP-GL plot past 16-bit coordinates gets a warning

Coordinates outside -32,768 to 32,767 plotter units (about 82 cm from the origin) are written as they are, with a warning that older devices may not read them. HP-GL/2 plots are not checked against this range.
This commit is contained in:
dotCooCoo
2026-11-12 00:12:54 -08:00
parent 9c5d22bccc
commit 2c9f5a6a11
4 changed files with 35 additions and 4 deletions
+8
View File
@@ -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.
//!
@@ -39,6 +41,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.
@@ -67,6 +71,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 TOO_DEEP: &str = "symbols, blends, envelopes and repeats nested more than 8 deep were left out";
@@ -261,6 +266,9 @@ pub fn export_regions(doc: &Document, opts: &HpglOptions, regions: &[Rect]) -> R
}
}
}
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),
+21
View File
@@ -454,6 +454,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());
+1 -1
View File
@@ -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
View File
@@ -6419,10 +6419,12 @@ are 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.
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.
```json
{"name":"export","arguments":{"path":"/tmp/decal.plt","format":"hpgl"}}