Skip to content

Commit 2912b17

Browse files
authored
Merge pull request #17 from u8array/feat/zpl-content-validation
Feat/zpl content validation
2 parents 1e21a72 + 9b638c1 commit 2912b17

30 files changed

Lines changed: 126 additions & 35 deletions

‎src/components/Canvas/BarcodeObject.tsx‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ export function BarcodeObject({
6666
bwipjs.toCanvas(canvas, opts as unknown as Parameters<typeof bwipjs.toCanvas>[1]);
6767
barcodeCanvas = canvas;
6868
} catch (e) {
69-
errorMsg = e instanceof Error ? e.message : String(e);
69+
const raw = e instanceof Error ? e.message : String(e);
70+
// bwip-js prefixes its errors like `bwip-js: bwipp.code39badCharacter: ...`
71+
// Strip the library/identifier head so the placeholder shows only the
72+
// human-readable reason.
73+
errorMsg = raw.replace(/^bwip-js:\s*/i, '').replace(/^bwipp\.[^:]+:\s*/i, '');
7074
}
7175
}
7276

@@ -589,9 +593,13 @@ export function BarcodeObject({
589593
<Text
590594
x={6}
591595
y={6}
596+
width={fbW - 12}
597+
height={fbH - 12}
598+
wrap="word"
599+
ellipsis
592600
text={errorMsg ? `⚠ ${errorMsg}` : obj.type}
593601
fontSize={Math.max(dotsToPx(10, scale, dpmm), 8)}
594-
fill="#374151"
602+
fill={errorMsg ? "#b91c1c" : "#374151"}
595603
/>
596604
</Group>
597605
);

‎src/components/Canvas/bwipHelpers.ts‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,15 @@ export function buildBwipOptions(
204204
case "codabar":
205205
case "plessey": {
206206
const p = obj.props;
207-
opts = { bcid, text: p.content || "0", scale: scale1D, height: 10 };
207+
// Code39/Codabar/Plessey only encode uppercase letters. Zebra firmware
208+
// (and Labelary) silently uppercase lowercase input; bwip-js does not and
209+
// throws instead, which would crash the canvas for any imported ZPL with
210+
// lowercase content. Uppercase here so the lib matches firmware behaviour
211+
// without rewriting the user's source.
212+
const needsUpper = obj.type === "code39" || obj.type === "codabar" || obj.type === "plessey";
213+
const raw = p.content || "0";
214+
const text = needsUpper ? raw.toUpperCase() : raw;
215+
opts = { bcid, text, scale: scale1D, height: 10 };
208216
break;
209217
}
210218
case "msi": {
@@ -221,9 +229,10 @@ export function buildBwipOptions(
221229
}
222230
case "logmars": {
223231
const p = obj.props;
232+
// LOGMARS is a Code39 subset; same uppercase rule applies.
224233
opts = {
225234
bcid,
226-
text: p.content || "0",
235+
text: (p.content || "0").toUpperCase(),
227236
scale: scale1D,
228237
height: 10,
229238
includecheck: true,

‎src/registry/aztec.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ObjectTypeDefinition } from "../types/ObjectType";
22
import { useT } from "../lib/useT";
33
import { inputCls, labelCls } from "../components/Properties/styles";
4-
import { fieldPos } from "./zplHelpers";
4+
import { fieldPos, fdField } from "./zplHelpers";
55

66
export interface AztecProps {
77
content: string;
@@ -27,7 +27,7 @@ export const aztec: ObjectTypeDefinition<AztecProps> = {
2727
return [
2828
fieldPos(obj),
2929
`^B0N,${p.magnification},N,N,N,N`,
30-
`^FD${p.content}^FS`,
30+
fdField(p.content),
3131
].join("");
3232
},
3333

‎src/registry/barcode1d.tsx‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { ObjectTypeDefinition, ObjectGroup, LabelObjectBase } from '../types/ObjectType';
22
import { useT } from '../lib/useT';
33
import { inputCls, labelCls } from '../components/Properties/styles';
4-
import { fieldPos } from './zplHelpers';
4+
import { fieldPos, fdField } from './zplHelpers';
55
import { commitHeightTransform } from './transformHelpers';
6+
import { filterContent, type ContentSpec } from './contentSpec';
67

78
export interface Barcode1DProps {
89
content: string;
@@ -16,7 +17,6 @@ interface Barcode1DConfig {
1617
label: string;
1718
icon: string;
1819
defaultContent: string;
19-
contentMaxLength?: number;
2020
hasCheckDigit: boolean;
2121
/** Build the ZPL barcode command (e.g. `^BUN,100,Y,N,N`). */
2222
zplCommand: (p: Barcode1DProps) => string;
@@ -34,6 +34,8 @@ interface Barcode1DConfig {
3434
heightLocked?: boolean;
3535
/** See {@link ObjectTypeDefinition.interpretationLocked}. */
3636
interpretationLocked?: boolean;
37+
/** Restrict allowed input characters; see {@link ContentSpec}. */
38+
contentSpec?: ContentSpec;
3739
}
3840

3941
interface BarcodeLocale {
@@ -77,7 +79,7 @@ export function createBarcode1D(config: Barcode1DConfig): ObjectTypeDefinition<B
7779
byCmd,
7880
fieldPos(obj),
7981
config.zplCommand(p),
80-
`^FD${p.content}^FS`,
82+
fdField(p.content),
8183
].filter(Boolean).join('');
8284
},
8385

@@ -92,9 +94,9 @@ export function createBarcode1D(config: Barcode1DConfig): ObjectTypeDefinition<B
9294
<input
9395
className={inputCls}
9496
value={p.content}
95-
maxLength={config.contentMaxLength}
97+
maxLength={config.contentSpec?.maxLength}
9698
placeholder={loc.placeholder}
97-
onChange={(e) => onChange({ content: e.target.value })}
99+
onChange={(e) => onChange({ content: filterContent(e.target.value, config.contentSpec) })}
98100
/>
99101
</div>
100102

‎src/registry/codabar.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const codabar = createBarcode1D({
88
hasCheckDigit: true,
99
localeKey: "codabar",
1010
group: 'code-1d',
11+
contentSpec: { charset: '0-9A-Da-d\\-$:/.+' },
1112
zplCommand: (p) => {
1213
const interp = p.printInterpretation ? "Y" : "N";
1314
const check = p.checkDigit ? "Y" : "N";

‎src/registry/codablock.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ObjectTypeDefinition } from "../types/ObjectType";
22
import { useT } from "../lib/useT";
33
import { inputCls, labelCls } from "../components/Properties/styles";
4-
import { fieldPos } from "./zplHelpers";
4+
import { fieldPos, fdField } from "./zplHelpers";
55
import { commitStacked2DTransform } from "./transformHelpers";
66

77
export interface CodablockProps {
@@ -32,7 +32,7 @@ export const codablock: ObjectTypeDefinition<CodablockProps> = {
3232
`^BY${p.moduleWidth}`,
3333
fieldPos(obj),
3434
`^BBN,${p.rowHeight},${p.securityLevel}`,
35-
`^FD${p.content}^FS`,
35+
fdField(p.content),
3636
]
3737
.filter(Boolean)
3838
.join("");

‎src/registry/code11.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const code11 = createBarcode1D({
88
hasCheckDigit: true,
99
localeKey: "code11",
1010
group: 'code-1d',
11+
contentSpec: { charset: '0-9\\-' },
1112
zplCommand: (p) => {
1213
const interp = p.printInterpretation ? "Y" : "N";
1314
const check = p.checkDigit ? "Y" : "N";

‎src/registry/code128.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ObjectTypeDefinition } from '../types/ObjectType';
22
import { useT } from '../lib/useT';
33
import { inputCls, labelCls } from '../components/Properties/styles';
4-
import { fieldPos } from './zplHelpers';
4+
import { fieldPos, fdField } from './zplHelpers';
55
import { commitHeightTransform } from './transformHelpers';
66

77
export interface Code128Props {
@@ -35,7 +35,7 @@ export const code128: ObjectTypeDefinition<Code128Props> = {
3535
`^BY${p.moduleWidth}`,
3636
fieldPos(obj),
3737
`^BCN,${p.height},${interp},N,${check}`,
38-
`^FD${p.content}^FS`,
38+
fdField(p.content),
3939
].filter(Boolean).join('');
4040
},
4141

‎src/registry/code39.tsx‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import type { ObjectTypeDefinition } from '../types/ObjectType';
22
import { useT } from '../lib/useT';
33
import { inputCls, labelCls } from '../components/Properties/styles';
4-
import { fieldPos } from './zplHelpers';
4+
import { fieldPos, fdField } from './zplHelpers';
55
import { commitHeightTransform } from './transformHelpers';
6+
import { filterContent, type ContentSpec } from './contentSpec';
7+
8+
const code39Spec: ContentSpec = { charset: '0-9A-Za-z\\-. $/+%' };
69

710
export interface Code39Props {
811
content: string;
@@ -35,7 +38,7 @@ export const code39: ObjectTypeDefinition<Code39Props> = {
3538
`^BY${p.moduleWidth}`,
3639
fieldPos(obj),
3740
`^B3N,${check},${p.height},${interp},N`,
38-
`^FD${p.content}^FS`,
41+
fdField(p.content),
3942
].filter(Boolean).join('');
4043
},
4144

@@ -49,7 +52,7 @@ export const code39: ObjectTypeDefinition<Code39Props> = {
4952
<input
5053
className={inputCls}
5154
value={p.content}
52-
onChange={(e) => onChange({ content: e.target.value })}
55+
onChange={(e) => onChange({ content: filterContent(e.target.value, code39Spec) })}
5356
/>
5457
</div>
5558

‎src/registry/contentSpec.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Per-symbology content rules. Applied on input so the canvas preview
3+
* (bwip-js) does not throw on characters the renderer rejects.
4+
*
5+
* Case normalisation is intentionally not done here: the source ZPL
6+
* is preserved verbatim, and `bwipHelpers` uppercases at the lib call
7+
* site for the symbologies that require it.
8+
*/
9+
export interface ContentSpec {
10+
/** Character-class body (no surrounding `[]`), e.g. `0-9` or `0-9A-Z\\-. $/+%`. */
11+
charset: string;
12+
maxLength?: number;
13+
}
14+
15+
const rejectCache = new WeakMap<ContentSpec, RegExp>();
16+
17+
function rejectPattern(spec: ContentSpec): RegExp {
18+
let re = rejectCache.get(spec);
19+
if (!re) {
20+
re = new RegExp(`[^${spec.charset}]`, 'g');
21+
rejectCache.set(spec, re);
22+
}
23+
return re;
24+
}
25+
26+
export function filterContent(raw: string, spec?: ContentSpec): string {
27+
if (!spec) return raw;
28+
const filtered = raw.replace(rejectPattern(spec), '');
29+
return spec.maxLength ? filtered.slice(0, spec.maxLength) : filtered;
30+
}

0 commit comments

Comments
 (0)