Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion src/components/Properties/PropertiesPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useLabelStore, useCurrentObjects } from "../../store/labelStore";
import { ObjectRegistry } from "../../registry";
import { stripZplCommandChars } from "../../registry/zplHelpers";
import { dotsToMm, mmToDots } from "../../lib/coordinates";
import {
mmToUnit,
Expand Down Expand Up @@ -137,7 +138,7 @@ export function PropertiesPanel() {
rows={2}
value={obj.comment ?? ""}
onChange={(e) =>
updateObject(obj.id, { comment: e.target.value || undefined })
updateObject(obj.id, { comment: stripZplCommandChars(e.target.value) || undefined })
}
/>
</div>
Expand Down
5 changes: 2 additions & 3 deletions src/lib/zplGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { mmToDots } from './coordinates';
import { ObjectRegistry } from '../registry';
import { stripZplCommandChars } from '../registry/zplHelpers';
import type { LabelConfig } from '../types/ObjectType';
import type { LabelObject } from '../registry';
import type { Page } from '../store/labelStore';
Expand Down Expand Up @@ -29,9 +30,7 @@ export function generateZPL(label: LabelConfig, objects: LabelObject[]): string
lines.push(...objects.map((obj) => {
const zpl = ObjectRegistry[obj.type]?.toZPL(obj) ?? '';
if (!obj.comment) return zpl;
// Strip ^ to prevent breaking ZPL structure inside the comment text
const safe = obj.comment.replace(/\^/g, '');
return `^FX${safe}\n${zpl}`;
return `^FX${stripZplCommandChars(obj.comment)}\n${zpl}`;
}));

if (label.printQuantity && label.printQuantity > 1) {
Expand Down
33 changes: 33 additions & 0 deletions src/lib/zplParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,39 @@ describe('parseZPL — ^FX comment', () => {
expect(objects).toHaveLength(1);
expect(skipped.some((s) => s.startsWith('^FX'))).toBe(false);
});

it('attaches a single ^FX to the next object as comment', () => {
const { objects } = parseZPL(
'^XA^FXTop section^FO10,20^A0N,30,0^FDText^FS^XZ',
8,
);
expect(objects[0]?.comment).toBe('Top section');
});

it('joins consecutive ^FX lines with a newline', () => {
const { objects } = parseZPL(
'^XA^FXLine 1^FXLine 2^FO10,20^A0N,30,0^FDText^FS^XZ',
8,
);
expect(objects[0]?.comment).toBe('Line 1\nLine 2');
});

it('does not bleed comments across ^XA boundaries', () => {
const { objects } = parseZPL(
'^XA^FXOnly first^XZ^XA^FO10,20^A0N,30,0^FDText^FS^XZ',
8,
);
expect(objects[0]?.comment).toBeUndefined();
});

it('does not reattach a consumed comment to a later object', () => {
const { objects } = parseZPL(
'^XA^FXOnly first^FO10,20^A0N,30,0^FDFirst^FS^FO10,60^A0N,30,0^FDSecond^FS^XZ',
8,
);
expect(objects[0]?.comment).toBe('Only first');
expect(objects[1]?.comment).toBeUndefined();
});
});

// ── ^FH hex encoding ──────────────────────────────────────────────────────────
Expand Down
13 changes: 11 additions & 2 deletions src/lib/zplParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,14 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL {
// ── Command handler map ────────────────────────────────────────────────────
const noop: Handler = () => void 0;
const resetComment: Handler = (_, rest) => { pendingComment = rest.trim() || undefined; };
// Hand-written ZPL often splits a logical comment across several `^FX` lines
// before the field they describe. Accumulate them so each line survives on
// the imported object's comment field; XA/XZ still reset at label boundaries.
const appendComment: Handler = (_, rest) => {
const next = rest.trim();
if (!next) return;
pendingComment = pendingComment ? `${pendingComment}\n${next}` : next;
};
const mkBrowserLimit = (prefix: string, delimiter = "^"): Handler => (_, rest) => {
const tok = `${delimiter}${prefix}${rest}`;
skipped.push(tok);
Expand Down Expand Up @@ -1083,8 +1091,9 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL {
// ^XA / ^XZ: label start/end — reset pending comment via empty rest
XA: resetComment,
XZ: resetComment,
// ^FX: comment field — store for attachment to the next field object
FX: resetComment,
// ^FX: comment field — accumulate across consecutive ^FX lines so the
// assembled text reaches the next field object as one multi-line comment.
FX: appendComment,

// These commands carry no canvas-design information and are silently
// discarded so they do not pollute importReport.unknown.
Expand Down
10 changes: 10 additions & 0 deletions src/registry/zplHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ export function fieldPos(obj: LabelObjectBase): string {
return `^${cmd}${obj.x},${obj.y}`;
}

/**
* Remove ZPL command/format prefixes from free-form text. `^FX` (comment) and
* other text-only contexts have no `^FH` escape mechanism, so these chars
* cannot be encoded — strip them so a stray `^` or `~` cannot terminate the
* surrounding command.
*/
export function stripZplCommandChars(s: string): string {
return s.replace(/[\^~]/g, '');
}

const FH_DELIM = '_';
const NEEDS_FH = /[\^~]/;

Expand Down