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
7 changes: 2 additions & 5 deletions src/components/Canvas/BarcodeObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useColorScheme } from "../../lib/useColorScheme";
import { selectionHandlers, type KonvaObjectProps } from "./konvaObjectProps";
import {
buildBwipOptions,
cleanBwipError,
getDisplaySize,
get1DBwipScale,
getEanUpcLayout,
Expand Down Expand Up @@ -74,11 +75,7 @@ export function BarcodeObject({
bwipjs.toCanvas(canvas, opts as unknown as Parameters<typeof bwipjs.toCanvas>[1]);
barcodeCanvas = canvas;
} catch (e) {
const raw = e instanceof Error ? e.message : String(e);
// bwip-js prefixes its errors like `bwip-js: bwipp.code39badCharacter: ...`
// Strip the library/identifier head so the placeholder shows only the
// human-readable reason.
errorMsg = raw.replace(/^bwip-js:\s*/i, '').replace(/^bwipp\.[^:]+:\s*/i, '');
errorMsg = cleanBwipError(e);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/components/Canvas/KonvaObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ const BARCODE_TYPES = new Set([
"planet",
"postal",
"aztec",
"maxicode",
"micropdf417",
"codablock",
"upcEanExtension",
Expand Down
26 changes: 21 additions & 5 deletions src/components/Canvas/bwipConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,27 @@ export const PLESSEY_BWIP_TO_ZEBRA_WIDTH_RATIO = 49 / 82;
* with the same module count but at different transition positions). UI
* surfaces a hint when one is selected so users know the layout is correct
* even when the visual rendering is approximate. */
export const BWIP_VISUAL_APPROX_TYPES = new Set<string>([
"code93",
"code11",
"plessey",
"gs1databar",
/** Severity of visual-render approximation per symbology. One
* axis with two levels:
* - "approx": bitmap approximate, dimensions spec-correct
* (we compute the bbox against the ZPL formula)
* - "unverified": bitmap approximate AND dimensions not cross-
* checkable against Labelary because Labelary
* returns the raw text payload instead of a real
* symbol render. The printed footprint may differ
* from the canvas bbox.
* The UI picks a per-severity hint string; absence means the
* canvas render is trusted. */
export type BwipApproxSeverity = "approx" | "unverified";

export const BWIP_APPROX_SEVERITY: ReadonlyMap<string, BwipApproxSeverity> = new Map([
["code93", "approx"],
["code11", "approx"],
["plessey", "approx"],
["gs1databar", "approx"],
["code49", "unverified"],
["codablock", "unverified"],
["maxicode", "unverified"],
]);

// Per-symbology spec module heights for GS1 DataBar. bwip-js renders most
Expand Down
61 changes: 61 additions & 0 deletions src/components/Canvas/bwipHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* bwipHelpers.test.ts ensures every BCID-registered type has a case.
*/

import bwipjs from "bwip-js/browser";
import { ObjectRegistry, type LeafObject } from "../../registry";
import type { LabelObject } from "../../types/Group";
import type { Gs1DatabarProps } from "../../registry/gs1databar";
Expand Down Expand Up @@ -92,6 +93,7 @@ const BCID: Partial<Record<LabelObject["type"], string>> = {
qrcode: "qrcode",
datamatrix: "datamatrix",
aztec: "azteccodecompact",
maxicode: "maxicode",
micropdf417: "micropdf417",
codablock: "codablockf",
// Placeholder — actual bcid (ean2 vs ean5) is resolved from the
Expand All @@ -103,6 +105,48 @@ const BCID: Partial<Record<LabelObject["type"], string>> = {
export const BWIP_SCALE = 2;
const BWIP_2D_INTERNAL_SCALE = 2;

/** Reused unattached canvas for dry-run encoding validators (e.g.
* `validateMaxicodeBwip`). Cheaper than creating a fresh element
* per call and we don't need its pixel output, just the success
* /throw signal from bwip-js. Initialised lazily so SSR-style
* imports don't break — `document` exists only in the browser. */
let _validationCanvas: HTMLCanvasElement | null = null;
function getValidationCanvas(): HTMLCanvasElement {
if (!_validationCanvas) _validationCanvas = document.createElement("canvas");
return _validationCanvas;
}

/** Strip the `bwip-js: bwipp.symbology:` prefixes that bwip-js
* decorates every encoder error with, so the bare diagnostic
* ("Expected postcode followed by group separator character",
* etc.) can be shown as-is in the UI. Shared between the canvas
* render (BarcodeObject) and dry-run validators below. */
export function cleanBwipError(e: unknown): string {
const raw = e instanceof Error ? e.message : String(e);
return raw.replace(/^bwip-js:\s*/i, "").replace(/^bwipp\.[^:]+:\s*/i, "");
}

/** Dry-run a Maxicode encode against the shared validation canvas
* to check whether the (content, mode) combination would succeed
* in the real render path. Returns null on success or the cleaned
* bwip-js error message on failure (e.g. SCM-format mismatch for
* modes 2/3). Uses the same option shape `buildBwipOptions`
* produces so canvas render and panel-side validation can't drift. */
export function validateMaxicodeBwip(content: string, mode: number): string | null {
try {
const opts = {
bcid: "maxicode",
text: content || " ",
scale: BWIP_SCALE,
mode,
} as unknown as Parameters<typeof bwipjs.toCanvas>[1];
bwipjs.toCanvas(getValidationCanvas(), opts);
return null;
} catch (e) {
return cleanBwipError(e);
}
}
Comment thread
u8array marked this conversation as resolved.

/**
* Estimate the number of columns ZPL/Labelary would choose for PDF417 when
* columns=0 (auto). Zebra uses a heuristic to keep the symbol somewhat square.
Expand Down Expand Up @@ -506,6 +550,14 @@ export function buildBwipOptions(
opts = { bcid, text: p.content || " ", scale: BWIP_SCALE };
break;
}
case "maxicode": {
const p = obj.props;
// bwip-js maxicode uses `mode` (number) directly; modes 2/3
// would also accept a structured carrier message via `parse`,
// but we don't expose SCM editing yet so pass content as-is.
opts = { bcid, text: p.content || " ", scale: BWIP_SCALE, mode: p.mode };
break;
}
case "micropdf417": {
const p = obj.props;
opts = {
Expand Down Expand Up @@ -902,6 +954,15 @@ function getUprightDisplaySize(
(cw / (BWIP_SCALE * BWIP_2D_INTERNAL_SCALE)) * modulePx;
return { w: size, h: size };
}
case "maxicode": {
// Fixed physical size — no module-magnification prop. bwip-js
// emits the canonical maxicode bitmap at BWIP_SCALE internal
// pixels per dot, so converting cw/ch back to dots and then
// to stage px places the symbol at its true printed size.
const w = dotsToPx(cw / BWIP_SCALE, scale, dpmm);
const h = dotsToPx(ch / BWIP_SCALE, scale, dpmm);
return { w, h };
}
case "micropdf417": {
const p = obj.props;
// bwip-js ignores rowheight for micropdf417 and always uses 2 internal pixels per row.
Expand Down
30 changes: 23 additions & 7 deletions src/components/Properties/PropertiesPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { LabelCanvasHandle } from "../Canvas/LabelCanvas";
import type { AlignAxis } from "../../lib/alignment";
import { ObjectRegistry } from "../../registry";
import { canGroupSelection, findObjectById, isGroup } from "../../types/Group";
import { BWIP_VISUAL_APPROX_TYPES } from "../Canvas/bwipConstants";
import { BWIP_APPROX_SEVERITY } from "../Canvas/bwipConstants";
import { stripZplCommandChars } from "../../registry/zplHelpers";
import { dotsToMm, mmToDots } from "../../lib/coordinates";
import {
Expand All @@ -29,6 +29,26 @@ import {
stripDrivePrefix,
} from "../../lib/customFonts";

/** Tooltip-icon flagging that the canvas render is approximate for
* the given object type. Two severities collapse into one icon
* with a per-severity hint string. Returns null when the type's
* render is fully trusted (no entry in BWIP_APPROX_SEVERITY). */
function BwipApproxIcon({ type }: { type: string }) {
const t = useT();
const severity = BWIP_APPROX_SEVERITY.get(type);
if (!severity) return null;
const title =
severity === "approx"
? t.properties.visualApproxHint
: t.properties.visualApproxUnverifiedHint;
return (
<InformationCircleIcon
className="w-3.5 h-3.5 text-muted/60 cursor-help"
title={title}
/>
);
}

/** POSITION section header with centre-on-label tools docked right —
* same Figma/Sketch/Affinity convention used across leaf, group and
* multi-select panels. `unitSuffix` shows only when X/Y inputs sit
Expand Down Expand Up @@ -152,12 +172,8 @@ export function PropertiesPanel({ canvasRef }: PropertiesPanelProps) {
<span className="text-xs font-medium text-text">
{typeLabel}
</span>
{BWIP_VISUAL_APPROX_TYPES.has(obj.type) && (
<InformationCircleIcon
className="w-3.5 h-3.5 text-muted/60 cursor-help"
title={t.properties.visualApproxHint}
/>
)}
<BwipApproxIcon type={obj.type} />

<span className="font-mono text-[10px] text-muted ml-auto truncate">
{obj.id.slice(0, 8)}
</span>
Expand Down
31 changes: 31 additions & 0 deletions src/lib/zplParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { DEFAULT_GS_SYMBOL, GS_SYMBOL_CODES, type SymbolProps } from "../registr
import type { SerialProps } from "../registry/serial";
import { isZplRotation, type ZplRotation } from "../registry/rotation";
import type { AztecProps } from "../registry/aztec";
import type { MaxicodeProps } from "../registry/maxicode";
import type { MicroPdf417Props } from "../registry/micropdf417";
import type { CodablockProps } from "../registry/codablock";
import { unzlibSync } from "fflate";
Expand Down Expand Up @@ -687,6 +688,9 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL {
// Aztec pending parameters
let aztecMag = 4;

// Maxicode pending parameters
let maxicodeMode: MaxicodeProps["mode"] = 4;

// MicroPDF417 pending parameters
let mpdfRowHeight = 10;

Expand Down Expand Up @@ -1118,6 +1122,22 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL {
),
);
break;
case "maxicode":
objects.push(
makeObj(
"maxicode",
x,
y,
{
content,
mode: maxicodeMode,
rotation: bcRotation,
} satisfies MaxicodeProps,
posType,
comment,
),
);
break;
case "micropdf417":
objects.push(
makeObj(
Expand Down Expand Up @@ -1492,6 +1512,17 @@ export function parseZPL(zpl: string, dpmm = 8): ParsedZPL {
// ^B0N,{magnification},... / ^BON,... — Aztec (^B0 and ^BO are synonyms)
B0: handleAztec,
BO: handleAztec,
// ^BVo,{mode},{symbolNumber},{totalSymbols} — Maxicode (fixed
// physical size, no magnification). symbolNumber/totalSymbols
// describe structured-append composition; we don't expose that
// in the editor, so the params are read but the emitted form
// pins them to (1, 1).
BV(p) {
fieldType = "maxicode";
bcRotation = readRotation(p[0]);
const m = int(p[1], 4);
maxicodeMode = (m >= 2 && m <= 6 ? m : 4) as MaxicodeProps["mode"];
},
// ^BFN,{rowHeight} — MicroPDF417
BF(p) {
fieldType = "micropdf417";
Expand Down
7 changes: 7 additions & 0 deletions src/locales/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const ar = {
planet: 'Planet Code',
postal: 'POSTNET',
aztec: 'Aztec',
maxicode: 'ماكسي كود',
micropdf417: 'MicroPDF417',
codablock: 'CODABLOCK',
group: 'مجموعة',
Expand All @@ -61,6 +62,7 @@ const ar = {
multipleSelectedHint: 'استخدم أسهم الاتجاه للتحريك',
groupSelection: 'تجميع التحديد',
visualApproxHint: 'العرض المرئي تقريبي؛ الأبعاد تطابق طباعة ZPL',
visualApproxUnverifiedHint: 'العرض البصري تقريبي؛ قد تختلف الأبعاد عن طباعة ZPL',
alignCenterH: 'توسيط أفقياً على الملصق',
alignCenterV: 'توسيط رأسياً على الملصق',
alignCenterBoth: 'توسيط على الملصق',
Expand Down Expand Up @@ -464,6 +466,11 @@ const ar = {
magnification: 'الحجم (1–10)',
ecLevel: 'تصحيح الأخطاء (0 = تلقائي)',
},
maxicode: {
content: 'المحتوى',
mode: 'الوضع',
mode6Advisory: 'الوضع 6 يقوم بترميز أوامر تكوين القارئ، وليس بيانات المستخدم',
},
micropdf417: {
content: 'المحتوى',
rowHeight: 'ارتفاع الصف (نقاط)',
Expand Down
7 changes: 7 additions & 0 deletions src/locales/bg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const bg = {
planet: 'Planet Code',
postal: 'POSTNET',
aztec: 'Aztec',
maxicode: 'Maxicode',
micropdf417: 'MicroPDF417',
codablock: 'CODABLOCK',
group: 'Група',
Expand All @@ -61,6 +62,7 @@ const bg = {
multipleSelectedHint: 'със стрелките местиш',
groupSelection: 'Групирай селекцията',
visualApproxHint: 'Визуалното изобразяване е приблизително; размерите съответстват на ZPL отпечатъка',
visualApproxUnverifiedHint: 'Визуалното изобразяване е приблизително; размерите може да се различават от ZPL отпечатъка',
alignCenterH: 'Хоризонтално центриране върху етикета',
alignCenterV: 'Вертикално центриране върху етикета',
alignCenterBoth: 'Центриране върху етикета',
Expand Down Expand Up @@ -464,6 +466,11 @@ const bg = {
magnification: 'Размер (1–10)',
ecLevel: 'Корекция на грешки (0 = auto)',
},
maxicode: {
content: 'Съдържание',
mode: 'Режим',
mode6Advisory: 'Режим 6 кодира команди за конфигурация на скенера, а не потребителски данни',
},
micropdf417: {
content: 'Съдържание',
rowHeight: 'Височина на ред (точки)',
Expand Down
7 changes: 7 additions & 0 deletions src/locales/cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const cs = {
planet: 'Planet Code',
postal: 'POSTNET',
aztec: 'Aztec',
maxicode: 'Maxicode',
micropdf417: 'MicroPDF417',
codablock: 'CODABLOCK',
group: 'Skupina',
Expand All @@ -61,6 +62,7 @@ const cs = {
multipleSelectedHint: 'šipkami posunete',
groupSelection: 'Seskupit výběr',
visualApproxHint: 'Vizuální zobrazení je přibližné; rozměry odpovídají tisku ZPL',
visualApproxUnverifiedHint: 'Vizuální vykreslení je přibližné; rozměry se mohou lišit od tisku ZPL',
alignCenterH: 'Vystředit vodorovně na štítku',
alignCenterV: 'Vystředit svisle na štítku',
alignCenterBoth: 'Vystředit na štítku',
Expand Down Expand Up @@ -464,6 +466,11 @@ const cs = {
magnification: 'Velikost (1–10)',
ecLevel: 'Korekce chyb (0 = auto)',
},
maxicode: {
content: 'Obsah',
mode: 'Režim',
mode6Advisory: 'Režim 6 kóduje konfigurační příkazy pro čtečku, nikoli uživatelská data',
},
micropdf417: {
content: 'Obsah',
rowHeight: 'Výška řádku (body)',
Expand Down
7 changes: 7 additions & 0 deletions src/locales/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const da = {
planet: 'Planet Code',
postal: 'POSTNET',
aztec: 'Aztec',
maxicode: 'Maxicode',
micropdf417: 'MicroPDF417',
codablock: 'CODABLOCK',
group: 'Gruppe',
Expand All @@ -61,6 +62,7 @@ const da = {
multipleSelectedHint: 'piletaster flytter',
groupSelection: 'Gruppér valg',
visualApproxHint: 'Visuel gengivelse er omtrentlig; dimensionerne svarer til ZPL-udskriften',
visualApproxUnverifiedHint: 'Visuel gengivelse er omtrentlig; mål kan afvige fra ZPL-udskriften',
alignCenterH: 'Centrer vandret på label',
alignCenterV: 'Centrer lodret på label',
alignCenterBoth: 'Centrer på label',
Expand Down Expand Up @@ -464,6 +466,11 @@ const da = {
magnification: 'Størrelse (1–10)',
ecLevel: 'Fejlkorrektion (0 = auto)',
},
maxicode: {
content: 'Indhold',
mode: 'Tilstand',
mode6Advisory: 'Tilstand 6 koder læserkonfigurationskommandoer, ikke brugerdata',
},
micropdf417: {
content: 'Indhold',
rowHeight: 'Rækkehøjde (punkter)',
Expand Down
Loading
Loading