Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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}
/>
);
}

interface PropertiesPanelProps {
/** Imperative handle on the canvas — used for actions that need live render
* bboxes (alignment, future zoom-to-selection, etc.). Required so the
Expand Down Expand Up @@ -124,12 +144,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