Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/lib/shapeGeometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,16 @@ export function outlineInset(
): OutlineInset {
const clampsToFilled = !filled && t * 2 >= Math.min(w, h);
const renderFilled = filled || clampsToFilled;
// Per-axis dimension promotion for solid rects: when the user-typed
// thickness exceeds an axis the printed field grows to thickness on
// that axis (Zebra firmware extrudes the outline outward in that
// case rather than clipping it to the declared bbox). Pure single-
// axis lines hit a different parser branch and are stored as `line`
// objects, so this only fires for ^GB rects where t > w or t > h.
return {
offset: renderFilled ? 0 : t / 2,
width: renderFilled ? w : Math.max(0, w - t),
height: renderFilled ? h : Math.max(0, h - t),
width: renderFilled ? Math.max(w, t) : Math.max(0, w - t),
height: renderFilled ? Math.max(h, t) : Math.max(0, h - t),
renderFilled,
};
}
Expand Down
19 changes: 8 additions & 11 deletions src/lib/shapeRender.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { LabelObject } from "../types/Group";
import { diagonalPolygonPoints } from "./shapeGeometry";
import { diagonalPolygonPoints, outlineInset } from "./shapeGeometry";

/** Inward-extruded ^GE / ^GC ring or solid disc, shared by ellipse and
* circle. Extracted so the two registry types — which carry different
Expand Down Expand Up @@ -74,18 +74,15 @@ export function renderShape(
// evenodd fill once we have a Labelary fixture with rounding>0
// to validate against; the current fixtures all use rounding=0
// so the four-band approach below is exact.
if (p.filled) {
ctx.fillStyle = color;
ctx.fillRect(obj.x, obj.y, p.width, p.height);
return;
}
const t = Math.max(1, p.thickness);
// Outline that extrudes inward — clamps to filled rect when the
// outline would meet itself in the middle (Zebra firmware does the
// same: ^GB with thickness >= min(w, h)/2 renders solid).
if (t * 2 >= Math.min(p.width, p.height)) {
const geom = outlineInset(p.width, p.height, t, p.filled);
Comment thread
u8array marked this conversation as resolved.
Outdated
if (geom.renderFilled) {
// `geom.width` / `geom.height` already account for the Zebra
// `^GB w,h,t` per-axis dimension promotion (max(w,t), max(h,t))
// so a thickness exceeding height or width extrudes the rendered
// field accordingly, matching Labelary.
ctx.fillStyle = color;
ctx.fillRect(obj.x, obj.y, p.width, p.height);
ctx.fillRect(obj.x, obj.y, geom.width, geom.height);
return;
}
// Four filled bands (top, bottom, left, right) avoid the
Expand Down
6 changes: 5 additions & 1 deletion src/lib/zplParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,7 +1012,11 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL {
{
width: w,
height: h,
thickness: filled ? 3 : t,
// Preserve the original thickness so a ZPL round-trip is
// lossless and the renderer can apply Zebra's dimension
// promotion (`max(w,t) × max(h,t)`) for fields where
// thickness exceeds the smaller axis.
thickness: t,
filled,
color,
rounding,
Expand Down
10 changes: 9 additions & 1 deletion src/registry/box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ export const box: ObjectTypeDefinition<BoxProps> = {

toZPL: (obj) => {
const p = obj.props;
const t = p.filled ? Math.min(p.width, p.height) : p.thickness;
// Emit `thickness` verbatim so a ZPL round-trip is lossless. Only
// floor it up to `min(w,h)` when the user toggled `filled` but the
// stored thickness is below the firmware's solid threshold; this
// keeps a user-driven "make this solid" intent in the printed
// output even if they never bumped the thickness slider.
const solidThreshold = Math.min(p.width, p.height);
const t = p.filled
? Math.max(p.thickness, solidThreshold)
: p.thickness;
return [
p.reverse ? '^LRY' : '',
fieldPos(obj),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions tests/fixtures/shapeTestCases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,61 @@ export const shapeTestCases: ShapeTestCase[] = [
zpl_input: "^XA^FO100,200^GD200,346,6,B,L^FS^XZ",
image_ref: "shape_line_diag_steep.png",
},

// Dimension-promotion cases for ^GB rects where thickness exceeds an
// axis. Zebra firmware extrudes solid fields out to `max(w, t)` /
// `max(h, t)`; renderShape used to draw the literal `w × h` and miss
// the strip along the affected edge. Each case picks a different
// axis so a regression touching only one branch is caught.
{
id: "shape_box_thickness_exceeds_height",
obj: {
id: "20",
type: "box",
// ^GB101,92,101: thickness > height → rect grows downward by
// (t - h) = 9 dots. Reproduces the user-reported case where the
// editor's box bottom was 9 dots above Labelary's. Reverse=true
// mirrors the original ZPL; ^LRY only inverts ink colour and
// does not affect geometry.
x: 144,
y: 160,
rotation: 0,
props: { width: 101, height: 92, thickness: 101, filled: false, color: "B", rounding: 0, reverse: true },
},
zpl_input: "^XA^LRY^FO144,160^GB101,92,101,B,0^FS^LRN^XZ",
image_ref: "shape_box_thickness_exceeds_height.png",
},
{
id: "shape_box_thickness_exceeds_width",
obj: {
id: "21",
type: "box",
// ^GB80,150,120: thickness > width → rect grows rightward by
// (t - w) = 40 dots. Symmetric of the above; catches a fix that
// only handles the height axis.
x: 100,
y: 100,
rotation: 0,
props: { width: 80, height: 150, thickness: 120, filled: false, color: "B", rounding: 0 },
},
zpl_input: "^XA^FO100,100^GB80,150,120,B,0^FS^XZ",
image_ref: "shape_box_thickness_exceeds_width.png",
},
{
id: "shape_box_thickness_exceeds_both",
obj: {
id: "22",
type: "box",
// ^GB60,40,90: thickness exceeds both axes → 90×90 square.
// Square is the firmware's documented "create a square" form
// (w, h, t all equal); the promotion path must collapse to that
// shape when t pulls both axes up to the same value.
x: 100,
y: 100,
rotation: 0,
props: { width: 60, height: 40, thickness: 90, filled: false, color: "B", rounding: 0 },
},
zpl_input: "^XA^FO100,100^GB60,40,90,B,0^FS^XZ",
image_ref: "shape_box_thickness_exceeds_both.png",
},
];