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
1,700 changes: 46 additions & 1,654 deletions src/lib/zplParser.ts

Large diffs are not rendered by default.

261 changes: 261 additions & 0 deletions src/lib/zplParser/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import type { LabelObject } from "../../types/Group";
import type { LabelConfig } from "../../types/ObjectType";
import type { PrinterProfile } from "../../types/PrinterProfile";
import type { Variable } from "../../types/Variable";
import type { TextProps } from "../../registry/text";
import type { SerialProps } from "../../registry/serial";
import type { Code49Props } from "../../registry/code49";
import type { Gs1DatabarProps } from "../../registry/gs1databar";
import type { DataMatrixProps } from "../../registry/datamatrix";
import type { MaxicodeProps } from "../../registry/maxicode";
import type { CodablockProps } from "../../registry/codablock";
import type { ZplRotation } from "../../registry/rotation";
import { DEFAULT_CLOCK_CHARS, type ClockChars } from "../fcTemplate";
import { getDecoder } from "./helpers";
import type { UploadedGraphic } from "./types";

/** Pending ^GB stash for the reverse-text collapse heuristic. */
export interface PendingReverseBg {
x: number;
y: number;
w: number;
h: number;
t: number;
color: "B" | "W";
rounding: number;
reverseFlag: boolean | undefined;
comment?: string;
}

/** Public output accumulators handed back via `ParsedZPL`. */
export interface ParserResult {
objects: LabelObject[];
labelConfig: Partial<LabelConfig>;
printerProfile: Partial<PrinterProfile>;
variables: Variable[];
skipped: string[];
partialCmds: Set<string>;
browserLimit: string[];
unknown: string[];
}

/** Label-frame state from ^LH / ^LT / ^LR; persists until overridden. */
export interface LabelFrameState {
lhX: number;
lhY: number;
ltY: number;
/** ^LR is label-wide; per-field ^FR lives on `field` (resets at ^FS). */
lrActive: boolean;
}

/** `fnComment` snapshots pending comment at ^FN so a later ^FX between
* ^FN and ^FS doesn't pollute the variable's auto-name. */
export interface CommentState {
pending: string | undefined;
fnNumber: number | null;
fnComment: string | undefined;
}

/** Format-scoped (per-^XA) state; reset at label boundary. */
export interface FormatState {
embedChar: string;
clockChars: ClockChars;
fhActive: boolean;
fhDelimiter: string;
fhDecoder: TextDecoder;
}

/** Persistent defaults for following fields (^CF, ^FW, ^FB, ^BY). */
export interface DefaultsState {
cfHeight: number;
cfWidth: number;
cfFontId: string | undefined;
fwRotation: TextProps["rotation"];
fbWidth: number;
fbLines: number;
fbSpacing: number;
fbJustify: TextProps["blockJustify"];
/** 0 = no ^BY height; barcode handlers fall back to 100. */
byModuleWidth: number;
byHeight: number;
}

/** ^CW aliases + ~DY uploads; span the whole parse across ^XA blocks. */
export interface FontsState {
aliases: Map<string, string>;
downloadedFontPaths: Set<string>;
downloadedGraphics: Map<string, UploadedGraphic>;
}

/** Per-field accumulator; consumed and reset by flushField at ^FS. */
export interface FieldState {
// Position (^FO / ^FT — pre-shift, before label.lh*/lt* offsets)
x: number;
y: number;
positionIsFT: boolean;
// Type discriminator + pending ^FD payload
fieldType: string | null;
pendingFD: string | null;
/** ^FR: single-field reverse, reset on ^FS / new ^FO / ^FT. */
frActive: boolean;
// Text-field cached params
textRot: TextProps["rotation"];
textH: number;
textW: number;
// Barcode pending (set by ^B*, consumed by flushField)
bcHeight: number;
bcInterp: boolean;
bcCheck: boolean;
bcRotation: ZplRotation;
bcCode49Mode: Code49Props["mode"];
// ^GS symbol pending
symRot: ZplRotation;
symH: number;
symW: number;
// GS1 Databar pending
gsSymbology: Gs1DatabarProps["symbology"];
gsSegments: number | undefined;
// 2D matrix pending
qrMag: number;
dmDim: number;
dmQuality: DataMatrixProps["quality"];
// Stacked-2D pending
pdfRowHeight: number;
pdfSecurity: number;
pdfColumns: number;
aztecMag: number;
maxicodeMode: MaxicodeProps["mode"];
mpdfRowHeight: number;
cbRowHeight: number;
cbSecurity: CodablockProps["securityLevel"];
// Pending font reference (^A@ or ^A{id}) — mutually exclusive
pendingPrinterFontName: string | undefined;
pendingFontId: string | undefined;
// ^SN / ^SF serialisation pending
snPending: boolean;
snIncrement: number;
snMode: SerialProps["zplMode"];
}

export interface ParserState {
result: ParserResult;
label: LabelFrameState;
comment: CommentState;
format: FormatState;
defaults: DefaultsState;
fonts: FontsState;
reverseBg: PendingReverseBg | null;
field: FieldState;
}

/** Dot tolerance for ^GB+^FR reverse-text collapse (rounding drift). */
export const REVERSE_BBOX_TOLERANCE_DOTS = 2;

/** Append to `skipped` and `browserLimit` (invariant: browserLimit ⊆ skipped). */
export function pushBrowserLimit(result: ParserResult, token: string): void {
result.skipped.push(token);
result.browserLimit.push(token);
}

/** Default text height: ^CF override, else ZPL baseline 30. */
export function getDefaultTextH(defaults: DefaultsState): number {
return defaults.cfHeight || 30;
}

/** Default text width: ^CF override, else 0 (= auto-from-height). */
export function getDefaultTextW(defaults: DefaultsState): number {
return defaults.cfWidth || 0;
}

/** ^FO vs ^FT discriminator for emit sites. */
export function getPosType(field: FieldState): "FT" | "FO" {
return field.positionIsFT ? "FT" : "FO";
}

export function createParserState(): ParserState {
return {
result: {
objects: [],
labelConfig: {},
printerProfile: {},
variables: [],
skipped: [],
partialCmds: new Set<string>(),
browserLimit: [],
unknown: [],
},
label: {
lhX: 0,
lhY: 0,
ltY: 0,
lrActive: false,
},
comment: {
pending: undefined,
fnNumber: null,
fnComment: undefined,
},
format: {
embedChar: "#",
clockChars: { ...DEFAULT_CLOCK_CHARS },
fhActive: false,
fhDelimiter: "_",
fhDecoder: getDecoder("utf-8"),
},
defaults: {
cfHeight: 0,
cfWidth: 0,
cfFontId: undefined,
fwRotation: "N",
fbWidth: 0,
fbLines: 1,
fbSpacing: 0,
fbJustify: "L",
byModuleWidth: 2,
byHeight: 0,
},
fonts: {
aliases: new Map<string, string>(),
downloadedFontPaths: new Set<string>(),
downloadedGraphics: new Map<string, UploadedGraphic>(),
},
reverseBg: null,
field: {
x: 0,
y: 0,
positionIsFT: false,
fieldType: null,
pendingFD: null,
frActive: false,
textRot: "N",
textH: 30,
textW: 0,
bcHeight: 100,
bcInterp: true,
bcCheck: false,
bcRotation: "N",
bcCode49Mode: "A",
symRot: "N",
symH: 30,
symW: 30,
gsSymbology: 1,
gsSegments: undefined,
qrMag: 4,
dmDim: 5,
dmQuality: 200,
pdfRowHeight: 10,
pdfSecurity: 0,
pdfColumns: 0,
aztecMag: 4,
maxicodeMode: 4,
mpdfRowHeight: 10,
cbRowHeight: 10,
cbSecurity: "Y",
pendingPrinterFontName: undefined,
pendingFontId: undefined,
snPending: false,
snIncrement: 1,
snMode: "SN",
},
};
}
Loading