Skip to content

Commit 9f3cdba

Browse files
authored
Merge pull request #18 from u8array/feat/comment-handling
Feat/comment handling
2 parents 2912b17 + 19e6f1e commit 9f3cdba

5 files changed

Lines changed: 58 additions & 6 deletions

File tree

‎src/components/Properties/PropertiesPanel.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useLabelStore, useCurrentObjects } from "../../store/labelStore";
22
import { ObjectRegistry } from "../../registry";
3+
import { stripZplCommandChars } from "../../registry/zplHelpers";
34
import { dotsToMm, mmToDots } from "../../lib/coordinates";
45
import {
56
mmToUnit,
@@ -137,7 +138,7 @@ export function PropertiesPanel() {
137138
rows={2}
138139
value={obj.comment ?? ""}
139140
onChange={(e) =>
140-
updateObject(obj.id, { comment: e.target.value || undefined })
141+
updateObject(obj.id, { comment: stripZplCommandChars(e.target.value) || undefined })
141142
}
142143
/>
143144
</div>

‎src/lib/zplGenerator.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { mmToDots } from './coordinates';
22
import { ObjectRegistry } from '../registry';
3+
import { stripZplCommandChars } from '../registry/zplHelpers';
34
import type { LabelConfig } from '../types/ObjectType';
45
import type { LabelObject } from '../registry';
56
import type { Page } from '../store/labelStore';
@@ -29,9 +30,7 @@ export function generateZPL(label: LabelConfig, objects: LabelObject[]): string
2930
lines.push(...objects.map((obj) => {
3031
const zpl = ObjectRegistry[obj.type]?.toZPL(obj) ?? '';
3132
if (!obj.comment) return zpl;
32-
// Strip ^ to prevent breaking ZPL structure inside the comment text
33-
const safe = obj.comment.replace(/\^/g, '');
34-
return `^FX${safe}\n${zpl}`;
33+
return `^FX${stripZplCommandChars(obj.comment)}\n${zpl}`;
3534
}));
3635

3736
if (label.printQuantity && label.printQuantity > 1) {

‎src/lib/zplParser.test.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,39 @@ describe('parseZPL — ^FX comment', () => {
192192
expect(objects).toHaveLength(1);
193193
expect(skipped.some((s) => s.startsWith('^FX'))).toBe(false);
194194
});
195+
196+
it('attaches a single ^FX to the next object as comment', () => {
197+
const { objects } = parseZPL(
198+
'^XA^FXTop section^FO10,20^A0N,30,0^FDText^FS^XZ',
199+
8,
200+
);
201+
expect(objects[0]?.comment).toBe('Top section');
202+
});
203+
204+
it('joins consecutive ^FX lines with a newline', () => {
205+
const { objects } = parseZPL(
206+
'^XA^FXLine 1^FXLine 2^FO10,20^A0N,30,0^FDText^FS^XZ',
207+
8,
208+
);
209+
expect(objects[0]?.comment).toBe('Line 1\nLine 2');
210+
});
211+
212+
it('does not bleed comments across ^XA boundaries', () => {
213+
const { objects } = parseZPL(
214+
'^XA^FXOnly first^XZ^XA^FO10,20^A0N,30,0^FDText^FS^XZ',
215+
8,
216+
);
217+
expect(objects[0]?.comment).toBeUndefined();
218+
});
219+
220+
it('does not reattach a consumed comment to a later object', () => {
221+
const { objects } = parseZPL(
222+
'^XA^FXOnly first^FO10,20^A0N,30,0^FDFirst^FS^FO10,60^A0N,30,0^FDSecond^FS^XZ',
223+
8,
224+
);
225+
expect(objects[0]?.comment).toBe('Only first');
226+
expect(objects[1]?.comment).toBeUndefined();
227+
});
195228
});
196229

197230
// ── ^FH hex encoding ──────────────────────────────────────────────────────────

‎src/lib/zplParser.ts‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,14 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL {
531531
// ── Command handler map ────────────────────────────────────────────────────
532532
const noop: Handler = () => void 0;
533533
const resetComment: Handler = (_, rest) => { pendingComment = rest.trim() || undefined; };
534+
// Hand-written ZPL often splits a logical comment across several `^FX` lines
535+
// before the field they describe. Accumulate them so each line survives on
536+
// the imported object's comment field; XA/XZ still reset at label boundaries.
537+
const appendComment: Handler = (_, rest) => {
538+
const next = rest.trim();
539+
if (!next) return;
540+
pendingComment = pendingComment ? `${pendingComment}\n${next}` : next;
541+
};
534542
const mkBrowserLimit = (prefix: string, delimiter = "^"): Handler => (_, rest) => {
535543
const tok = `${delimiter}${prefix}${rest}`;
536544
skipped.push(tok);
@@ -1083,8 +1091,9 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL {
10831091
// ^XA / ^XZ: label start/end — reset pending comment via empty rest
10841092
XA: resetComment,
10851093
XZ: resetComment,
1086-
// ^FX: comment field — store for attachment to the next field object
1087-
FX: resetComment,
1094+
// ^FX: comment field — accumulate across consecutive ^FX lines so the
1095+
// assembled text reaches the next field object as one multi-line comment.
1096+
FX: appendComment,
10881097

10891098
// These commands carry no canvas-design information and are silently
10901099
// discarded so they do not pollute importReport.unknown.

‎src/registry/zplHelpers.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ export function fieldPos(obj: LabelObjectBase): string {
66
return `^${cmd}${obj.x},${obj.y}`;
77
}
88

9+
/**
10+
* Remove ZPL command/format prefixes from free-form text. `^FX` (comment) and
11+
* other text-only contexts have no `^FH` escape mechanism, so these chars
12+
* cannot be encoded — strip them so a stray `^` or `~` cannot terminate the
13+
* surrounding command.
14+
*/
15+
export function stripZplCommandChars(s: string): string {
16+
return s.replace(/[\^~]/g, '');
17+
}
18+
919
const FH_DELIM = '_';
1020
const NEEDS_FH = /[\^~]/;
1121

0 commit comments

Comments
 (0)