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
4 changes: 2 additions & 2 deletions packages/core/src/lib/code128Plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { C0_RE, resolveControlMarkers } from "../types/controlKey";
export type Code128FdMode = "whole" | "template" | "templateValue" | "sharedRaw";

export type Code128FdLoss =
/** Control bytes cannot ride this mode's FD: they fall to ^FH, which the
* firmware drops from the symbol (ZD230). */
/** Control bytes cannot ride this mode's FD, they fall to ^FH, see
* code128Subset. */
| { kind: "controlBytesDropped" }
/** `>` before an invocation char stays verbatim (import round-trip channel),
* so the firmware reads an invocation, not text. */
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/lib/code128Subset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,7 @@ describe("printed vs rendered module count", () => {
});

it("drops the control bytes a template payload cannot print", () => {
// The ^FE path keeps the ^FH escape, whose C0 bytes the firmware strips
// from the symbol; the preview must show the same symbol.
// The preview must show the symbol the printer prints.
const obj = code128("A«sku»B«ctrl:TAB»");
const variables = [{ id: "v1", name: "sku", fnNumber: 1, defaultValue: "X" }];
const resolved = resolveForMeasure(obj as LabelObject, variables);
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/lib/designFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,6 @@ function migrateReverseTextBackground(json: unknown): void {
const p = pg && typeof pg === "object" ? (pg as Record<string, unknown>) : {};
if (!Array.isArray(p.objects)) return p;
const objects = p.objects as LabelObject[];
// Inserting a model object the overlay doesn't link would force a full
// regeneration on export and lose the overlay's preserved bytes; drop the
// overlay on a touched page so it regenerates cleanly from the new model.
const next: Record<string, unknown> = {
...p,
objects: insertReverseBackingBoxes(objects, label),
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/lib/emittedAnchor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { objectBoundsDots, type BoundingBoxDots, type ObjectBoundsCtx } from "./
/** The ^FO/^FT origin (dots) the generator actually emits for this leaf, i.e.
* the coordinate the printer sees. The single source the off-label preflight
* shares with emission, so "is the position off-label" can't drift from what
* prints: text uses the cap-top/baseline transform, graphics anchor at their
* bbox (top-left under ^FO, a bottom corner under ^FT), barcodes/symbol emit at
* the model coord (a gated R 1D at the measured right edge). Pass `box` when the caller already has the bbox to skip a
* recompute. Known minor gap: an ^FT right-justified diagonal line or image uses
* prints: text uses the cap-top/baseline transform, graphics go through
* {@link graphicAnchorCoords}, barcodes/symbol emit at the model coord, a
* gated R 1D at the measured right edge. Pass `box` when the caller already
* has the bbox to skip a recompute. Known minor gap: an ^FT right-justified diagonal line or image uses
* the visual bbox width, a few dots off the emitted ^GD/^GF width, which only
* shifts the near-left edge for a field whose right corner sits at ~x=0. */
export function emittedAnchorDots(
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/lib/gfaDecode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ describe("the header count, not the stream", () => {
});

it("refuses a header without the count, which prints nothing", () => {
// Labelary: omitting b renders identically, omitting c produces no label
// at all, because the firmware never learns where the graphic ends.
// Labelary: omitting b renders identically, omitting c produces no label.
expect(rasterFromGfa("^GFA,,,2,C3D4FFFF")).toBeNull();
expect(rasterFromGfa("^GFA,,4,2,C3D4FFFF")?.heightDots).toBe(2);
});
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/lib/labelGeometry/deviceFonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ function deviceFontMags(

/** Deterministic field extent in dots for the ^FO anchor of rotated (I/B)
* bitmap fields: cells are monospaced, and Labelary measures the printed
* extent at n*advance - gap/2 within 2 dots across fonts. Null for
* Font 0 / unknown ids (measured PrintLab width applies there). */
* extent at n*advance - gap/2 within 2 dots across fonts. Null as in
* deviceFontMags, the measured PrintLab width applies there. */
export function deviceFontInkWidthDots(
fontId: string | undefined,
heightDots: number,
Expand All @@ -102,8 +102,8 @@ export function deviceFontInkWidthDots(
return magW * (n * spec.advancePerMag - gap / 2);
}

/** Snapped character-cell width (the effective ^A font width), or null
* for Font 0 / unknown ids. */
/** Snapped character-cell width (the effective ^A font width), or null as in
* deviceFontMags. */
export function deviceFontSnappedWidthDots(
fontId: string | undefined,
heightDots: number,
Expand All @@ -113,9 +113,9 @@ export function deviceFontSnappedWidthDots(
return mags ? mags.magW * mags.spec.magWidthStep : null;
}

/** Requested ^A height snapped to the cell grid, or null for Font 0 /
* unknown ids / non-positive heights. The firmware anchors a bitmap field
* by its snapped cell, so the anchor transform needs this too. */
/** Requested ^A height snapped to the cell grid, null as in deviceFontMags.
* The firmware anchors a bitmap field by its snapped cell, so the anchor
* transform needs this too. */
export function deviceFontSnappedHeightDots(
fontId: string | undefined,
heightDots: number,
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/lib/objectTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ interface UnknownNode {

/** Apply `visit` to every leaf (non-group) node found under `pages`.
* `pages` is treated permissively: any nested `objects`/`children`
* arrays are walked. Mutates leaves in-place via the visitor. */
* arrays are walked. Mutates leaves in place, which is safe because
* the callers hand it the persisted payload, never the live store. */
export function visitLeavesInPages(pages: unknown, visit: (leaf: UnknownNode) => void): void {
if (!Array.isArray(pages)) return;
for (const page of pages) walkNode(page, visit);
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/lib/qrFd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ describe("qrFdToModel", () => {
});
}

// ZD230-measured: a count that does not land on a comma loses only the
// overhanging bytes; the firmware resyncs at the next comma and reads on
// (`QM,B0002abc,N12` prints `ab12`).
it("resyncs at the next comma when a byte count overruns its segment", () => {
expect(qrFdToModel("QM,B0002abc,N12").content).toBe("ab12");
});
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/lib/reverseBacking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,9 @@ export function precedingBackingExists(
return false;
}

/** True when a page has at least one reverse text that the migration would give
* a new backing (i.e. none covering it already). The migration uses this to
* invalidate that page's overlay, since inserting a model object the overlay
* doesn't know about would otherwise silently force a full regeneration. */
/** True when a page has at least one reverse text with no backing covering it
* yet, so the migration would add one. The caller drops that page's overlay,
* see designOps. */
export function pageNeedsReverseBacking(
objects: readonly LabelObject[],
label?: FontLabel,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/lib/rfidPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface RfidPositionValue {
}

/** Media-motion distance the position declares, in mm off the label top.
* Dots are physical head dots like ^ML (^JM does not scale the feed). */
* Dots are physical head dots like ^ML, ^JM does not scale the feed. */
export function rfidPositionValue(
position: string | undefined,
dpmm: number,
Expand Down
9 changes: 0 additions & 9 deletions packages/core/src/lib/variableField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ import { extractClockTokens } from "./fcTemplate";
export type BindableLeaf = LabelObjectBase & { props: { content: string } };
export const asLabelObject = (obj: BindableLeaf): LabelObject => obj as unknown as LabelObject;

/**
* Single source of truth for the unified token field. `content` is the only
* state: content that is exactly one known variable marker is Single-Bind
* (emits the inline `^FN{n}^FD{default}^FS`); any other `«name»`/`«clock:T»`
* marker shape is Template; no markers is Literal. Preview
* (`applyBindingToObject`) and export (`fdFieldFor`) consume the same
* `classifyField` decision, so they can never diverge.
*/

/** True when content is exactly one `«marker»` and nothing else: the shape that
* emits inline (single-bind) rather than expanding to `^FE` embeds. */
export function isLoneMarker(content: string): boolean {
Expand Down
9 changes: 4 additions & 5 deletions packages/core/src/lib/zebraTextLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,10 @@ export function tbBlockExtentDots(
return Math.max(0, blockHeightDots - effH * (1 - EM_TOP_ABOVE_CAP));
}

/** Anchor extent of a block beyond its first line, ^TB or ^FB. The single
* tb-vs-fb dispatch shared by generator, parser and the box-match gate;
* a one-sided change here shifts anchors silently. tbHeightDots > 0
* selects ^TB (the parser's native signal: ^TB is stream state, not a
* stored mode). */
/** Anchor extent of a block beyond its first line: ^TB is a fixed clip height,
* ^FB stacks lines. The single tb-vs-fb dispatch shared by generator, parser
* and the box-match gate, so a one-sided change shifts anchors silently.
* tbHeightDots > 0 selects ^TB, the parser's native signal. */
export function blockAnchorExtentDots(args: {
tbHeightDots: number;
blockWidthDots: number;
Expand Down
14 changes: 4 additions & 10 deletions packages/core/src/lib/zplGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ export function planTemplateHeader(
}
// Mode-D-exclusive slot: > needs its >0 invocation (parser reverses).
// Plain-^BC-exclusive slot likewise: >/^/~ need their invocation
// literals; the ^FH hex fdField would otherwise use is dropped from
// the symbol.
// literals, see code128Subset.
const def = modeDFns.has(fn)
? escapeGs1FdValue(v.defaultValue)
: plain128Fns.has(fn)
Expand Down Expand Up @@ -926,11 +925,6 @@ export function generateBatchZpl(
return [...(lead === '' ? [] : [lead]), ...recallBlocks].join('\n');
}

// ^FT graphics anchor at a bottom corner (spec p.205), so the drop test uses
// that anchor for GRAPHIC_ANCHOR_TYPES. Barcodes emit ^FT at the model coord
// (the plain check holds); rotated text ^FT uses a baseline anchor, where the
// top-left check is only approximate (a pre-existing edge, untouched here).

/** Home-relative to a parsed ^LH/^LT frame, not the label's own home/top (see OverlayFrame). */
export function shiftIntoFrame(objects: LabelObject[], frame: OverlayFrame | undefined, label: PageLabel): LabelObject[] {
return frame ? shiftObjectsByHome(objects, frame.homeX, frame.homeY, frame.top, label) : objects;
Expand Down Expand Up @@ -978,6 +972,7 @@ function shiftObjectsByHome(
const ax = printerAnchoredX(obj, label);
if (ax !== null) return ax - homeX < 0 || y < 0 ? [] : [{ ...obj, x, y }];
}
// ^FT text emits at a baseline, so for it this top-left check is approximate.
return x < 0 || y < 0 ? [] : [{ ...obj, x, y }];
};
return objects.flatMap(shiftOrDrop);
Expand Down Expand Up @@ -1042,9 +1037,8 @@ function generateZplBlock(
/** Shared across a document's blocks so a font ships once. A replayed block's own ~DY stays invisible to it, which costs size only. */
shippedFonts = new Set<string>(),
): PageBlock {
// ^PW/^LL are consumed in physical head dots (ZD230-verified), even under
// ^JMB: the body's object dots emit in the effective (halved) scale, but the
// print width/length stay physical.
// ^PW/^LL are physical head dots, see deriveUnitScale, while the body's object
// dots emit in the effective ^JM scale.
const widthDots = mmToDots(label.widthMm, label.dpmm);
const heightDots = mmToDots(label.heightMm, label.dpmm);

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/lib/zplImportService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ function foldRecallPages(pages: readonly ParsedPage[]): RecallFold | null {
}

export function importZplText(zpl: string, dpmm: number): ZplImportResult {
// Single pass: stream-persistent state (^MU, ^CC/^CT/^CD, ^CI, ^CW/uploads,
// ^CF/^BY, ^LH/^LT/^LR) carries across ^XA blocks, and the parser owns the
// page boundaries (prefix-aware, unlike a literal ^XA split).
// Single pass: stream-persistent state carries across ^XA blocks, see
// resetFormatScopedState, and the parser owns the page boundaries,
// prefix-aware unlike a literal ^XA split.
const r = parseZPL(zpl, dpmm, { captureOverlay: true });
// storedGraphicShips below reads the cache.
const imageOwner = newId();
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/lib/zplParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,8 +557,7 @@ export function parseZPL(

const dispatch = ({ cmd, rest, start, end }: ZplToken): void => {
// Read by the finding push sites (notePartial, pushBrowserLimit). End is
// trimmed: the tokenizer's end runs to the next command's prefix, and a
// span must not swallow the line break plus following indent.
// trimmed because the tokenizer's end runs to the next command's prefix.
s.result.prevTokenEnd = s.result.tokenSpan?.end ?? 0;
s.result.tokenSpan = { start, end: trimmedSpanEnd(zpl, start, end) };
s.result.tokenEnd = end;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/lib/zplParser/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,9 @@ export interface FieldState {
/** ^FN seen since this opener. Such a slot dies with the field, while a slot
* armed before the opener survives it, measured on a format recalled by ^XF. */
fnArmedSinceOpener: boolean;
/** ^FE armed for the next ^FD only (spec p.191); reset at ^FS. */
/** See consumeOneShotArms. */
feArmed: boolean;
/** ^FC armed for the next ^FD only (spec p.1614); reset at ^FS. */
/** See consumeOneShotArms. */
fcArmed: boolean;
/** ^FR: single-field reverse, reset at ^FS. */
frActive: boolean;
Expand Down
14 changes: 5 additions & 9 deletions packages/core/src/lib/zplParser/flushField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,6 @@ export function createCloseField(
const hexControl = recallValue === undefined ? s.field.pendingHexControl : (s.declaredHexControl.get(s.comment.fnNumber ?? -1) ?? null);
if (hexControl && truncatesAtHexControl(s, rawDecoded)) s.result.hexControl.push(hexControl);
// FN embeds → markers, then ^FC clock tokens (order matters: `%FN#1#%Y`).
// Both decode only when armed for THIS field: firmware honours ^FE/^FC
// solely for the next ^FD (spec p.191/p.1614), unarmed data is literal.
// Taken as a function because ^BQ replays it on a different string.
const expandTokens = (raw: string): string => {
const withFn = s.field.feArmed ? applyFnEmbeds(raw) : raw;
Expand Down Expand Up @@ -287,10 +285,9 @@ export function createCloseField(
if (s.field.fieldType !== "text") commitPendingReverseBg();
switch (s.field.fieldType) {
case "text": {
// FT pins the last baseline, so the EM-top sits one block-extent above;
// FO R/I stack the same way. ^TB is a fixed clip height, ^FB stacks
// lines. Must match the generator's blockExtentFor for byte-exact
// round-trips and matching WYSIWYG.
// FT pins the last baseline, so the EM-top sits one block-extent above,
// FO R/I stack the same way. Must match the generator's blockExtentFor
// for byte-exact round-trips and matching WYSIWYG.
// Same resolution as the generator so anchors round-trip.
const anchorFontId = resolveDeviceFontId(
s.field.pendingFontId,
Expand Down Expand Up @@ -751,9 +748,8 @@ export function createCloseField(
const variable = upsertVariable(s.comment.fnNumber, varDefault, s.comment.fnComment);
// Serial wins over the binding, but only when actually applied
// (serialisable type); else a 2D field's ^FN binding would be silently
// dropped. The upsert still runs: the slot is shared, so a later field
// or embed may reference it and must see this default. If none does,
// the end-of-parse sweep drops the orphan.
// dropped. The upsert still runs so a later reference sees this
// default.
const serialApplied = s.field.snPending && lastSerialisable;
if (serialApplied) {
s.serialStrippedFns.add(s.comment.fnNumber);
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/lib/zplParser/handlers/barcodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ export function createBarcodeHandlers(s: ParserState): Record<string, Handler> {
: "A";
},

// MSI ^BMN,{e},{h},{f},{g},{e2}; e: A=no check, B/C/D=checked (default
// B; the firmware also treats invalid values as B, ZD230-measured).
// MSI ^BMN,{e},{h},{f},{g},{e2}. The firmware reads an invalid e as B,
// ZD230-measured, see msiCoreConfig for the e values.
BM(p) {
s.field.fieldType = "msi";
s.field.bcRotation = readRotation(p[0]);
Expand Down Expand Up @@ -218,8 +218,7 @@ export function createBarcodeHandlers(s: ParserState): Record<string, Handler> {
s.field.cbColumns = clampCodablockColumns(int(p[3], 0) || undefined);
},

// ^BTo,w1,r1,h1,w2,h2 (spec p.140). The MicroPDF row count has no
// param: the firmware derives it from the serial (always 4 columns).
// ^BTo,w1,r1,h1,w2,h2 (spec p.140). The row count has no param, see Tlc39MicroPdf.
BT(p) {
s.field.fieldType = "tlc39";
s.field.bcRotation = readRotation(p[0]);
Expand Down
14 changes: 4 additions & 10 deletions packages/core/src/lib/zplParser/handlers/graphics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,7 @@ export function createGraphicsHandlers(s: ParserState, helpers: GraphicsHelpers)
color: "B" | "W", rounding: number, reverseFlag: boolean | undefined,
comment: string | undefined, positionType: "FO" | "FT", justify: "L" | "R",
): LabelObject => {
// ^FT graphic origin is a bottom corner (spec p.205): bottom-left, or
// bottom-right when z=1 (justify R). The model stores the top-left, so
// lift by the height and, for R, shift left by the width. ^FO is top-left.
const topLeftY = positionType === "FT" ? gy - h : gy;
const topLeftX = positionType === "FT" && justify === "R" ? gx - w : gx;
const { x: topLeftX, y: topLeftY } = ftTopLeft(gx, gy, w, h, positionType, justify);
let obj;
if (h === t && w > t) {
obj = makeObj(
Expand Down Expand Up @@ -315,9 +311,7 @@ export function createGraphicsHandlers(s: ParserState, helpers: GraphicsHelpers)
const gdColor = readColor(p[3]);
const gdOri = (p[4] ?? "L").toUpperCase();
const gdLen = Math.round(Math.sqrt(gdW * gdW + gdH * gdH));
// ^FT anchors the ^GD bounding box at a bottom corner; lift to the box
// top-left before recovering the start point (gdOri is the diagonal
// direction, distinct from the z-justify "R").
// gdOri is the diagonal direction, distinct from the z-justify "R".
const posType = getPosType(s.field);
const justify = s.field.justify;
const { x: boxX, y: boxY } = ftTopLeft(s.field.x, s.field.y, gdW, gdH, posType, justify);
Expand Down Expand Up @@ -535,8 +529,8 @@ export function createGraphicsHandlers(s: ParserState, helpers: GraphicsHelpers)
if (parsed) recallStoredGraphic("^IL", parsed, { x: s.label.lhX, y: s.label.lhY + s.label.ltY });
},

// ^GS{rotation},{height},{width}: selects the internal-font
// legal-symbol glyph (^FD picks which: A=®, B=©, C=™, D=UL, E=CSA).
// ^GS{rotation},{height},{width}: selects the internal-font legal-symbol
// glyph, ^FD picks which, see SymbolProps.symbol.
GS(p) {
s.field.fieldType = "symbol";
s.field.symRot = readRotation(p[0]);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/lib/zplParser/handlers/labelConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export function createLabelConfigHandlers(
): Record<string, Handler> {
const labelConfig = s.result.labelConfig;
const { dotsOrUndef } = dotsFor(s);
// ^PW/^LL/^ML are physical head dots, ^JM-independent (ZD230-verified): omit
// jmDensity from the scale so the un-halved ^MU multiplier applies.
// ^PW/^LL/^ML read at the physical density, see deriveUnitScale, so the
// scale omits jmDensity and the un-halved ^MU multiplier applies.
const physDots = (raw: string | undefined): number | undefined =>
intDotsOrUndef(raw, deriveUnitScale({ muMode: s.format.muMode }, dpmm));
return {
Expand Down
6 changes: 2 additions & 4 deletions packages/core/src/lib/zplParser/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,8 @@ export function makeObj(
} as unknown as LabelObject;
}

/** Convert an imported graphic anchor to the model's top-left. `^FT` anchors a
* bottom corner (spec p.205): bottom-left, or bottom-right (z=1) when justify
* is "R". Lift by the height and, for "R", shift left by the width. `^FO` is
* already top-left. Mirrors the generator's {@link graphicAnchor}. */
/** Convert an imported graphic anchor to the model's top-left, the inverse of
* the generator's {@link graphicAnchorCoords}. */
export function ftTopLeft(
x: number,
y: number,
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/registry/aztec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export interface AztecProps {
rotation: ZplRotation;
}

// One switch for the capability flag and the emitter's chip resolution.
const CONTROL_CHARS = true;

const magnificationTooSmall = moduleTooSmallPreflight<AztecProps>('magnification');
Expand Down
Loading
Loading