Skip to content

Commit dd1e7ba

Browse files
feat(barcode-generator-web): render Data Matrix via bwip-js
Adds DataMatrixRenderer as a third render path. Selects the bwip-js encoder by GS1 mode and shape (datamatrix / gs1datamatrix / *rectangular), renders inline SVG, and reuses the existing SVG->PNG download pipeline. Wires the dispatch in BarcodeGenerator and the download filename prefix for the datamatrix type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c654190 commit dd1e7ba

3 files changed

Lines changed: 116 additions & 1 deletion

File tree

packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import classNames from "classnames";
22
import { ReactElement } from "react";
33
import { BarcodeGeneratorContainerProps } from "../typings/BarcodeGeneratorProps";
44
import { BarcodeRenderer } from "./components/Barcode";
5+
import { DataMatrixRenderer } from "./components/DataMatrix";
56
import { QRCodeRenderer } from "./components/QRCode";
67
import { barcodeConfig } from "./config/Barcode.config";
78

@@ -22,7 +23,13 @@ export default function BarcodeGenerator(props: BarcodeGeneratorContainerProps):
2223
tabIndex={props.tabIndex}
2324
style={props.style}
2425
>
25-
{config.type === "qrcode" ? <QRCodeRenderer config={config} /> : <BarcodeRenderer config={config} />}
26+
{config.type === "qrcode" ? (
27+
<QRCodeRenderer config={config} />
28+
) : config.type === "datamatrix" ? (
29+
<DataMatrixRenderer config={config} />
30+
) : (
31+
<BarcodeRenderer config={config} />
32+
)}
2633
</div>
2734
);
2835
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {
2+
datamatrix,
3+
datamatrixrectangular,
4+
drawingSVG,
5+
gs1datamatrix,
6+
gs1datamatrixrectangular
7+
} from "@bwip-js/browser";
8+
import { ReactElement, useMemo, useRef } from "react";
9+
import { DownloadButton } from "./DownloadButton";
10+
import { DataMatrixTypeConfig } from "../config/Barcode.config";
11+
import { validateBarcodeValue, validateGs1DataMatrixValue } from "../config/validation";
12+
import { downloadCode } from "../utils/download-code";
13+
import { printError } from "../utils/helpers";
14+
15+
interface DataMatrixRendererProps {
16+
config: DataMatrixTypeConfig;
17+
}
18+
19+
/** Selects the bwip-js encoder for the requested GS1 mode and symbol shape. */
20+
function encodeDataMatrix(config: DataMatrixTypeConfig): string {
21+
const opts = {
22+
text: config.codeValue,
23+
// bwip-js scale is in module units; map the pixel size onto a reasonable scale.
24+
scale: Math.max(1, Math.round(config.size / 32)),
25+
paddingwidth: config.margin,
26+
paddingheight: config.margin,
27+
// GS1 AI syntax uses parentheses; parse must be on for the human-readable form.
28+
parse: config.gs1Mode
29+
} as const;
30+
31+
if (config.gs1Mode) {
32+
return config.shape === "rectangle"
33+
? gs1datamatrixrectangular({ ...opts, bcid: "gs1datamatrixrectangular" }, drawingSVG())
34+
: gs1datamatrix({ ...opts, bcid: "gs1datamatrix" }, drawingSVG());
35+
}
36+
37+
return config.shape === "rectangle"
38+
? datamatrixrectangular({ ...opts, bcid: "datamatrixrectangular" }, drawingSVG())
39+
: datamatrix({ ...opts, bcid: "datamatrix" }, drawingSVG());
40+
}
41+
42+
export function DataMatrixRenderer({ config }: DataMatrixRendererProps): ReactElement {
43+
const containerRef = useRef<HTMLDivElement>(null);
44+
const { codeValue, downloadButton, size, gs1Mode } = config;
45+
const buttonPosition = downloadButton?.buttonPosition ?? "bottom";
46+
47+
const { svg, error } = useMemo<{ svg: string | null; error: boolean }>(() => {
48+
if (!codeValue) {
49+
return { svg: null, error: false };
50+
}
51+
52+
const baseValidation = validateBarcodeValue("DataMatrix", codeValue);
53+
if (!baseValidation.valid) {
54+
printError(`Validation failed for Data Matrix: ${baseValidation.message}`, config.logLevel);
55+
return { svg: null, error: true };
56+
}
57+
58+
if (gs1Mode) {
59+
const gs1Validation = validateGs1DataMatrixValue(codeValue);
60+
if (!gs1Validation.valid) {
61+
printError(`GS1 Data Matrix validation failed: ${gs1Validation.message}`, config.logLevel);
62+
return { svg: null, error: true };
63+
}
64+
}
65+
66+
try {
67+
return { svg: encodeDataMatrix(config), error: false };
68+
} catch (e) {
69+
const message = e instanceof Error ? e.message : "Error generating Data Matrix";
70+
printError(`Rendering failed: ${message} \nValue: "${codeValue}"`, config.logLevel);
71+
return { svg: null, error: true };
72+
}
73+
// eslint-disable-next-line react-hooks/exhaustive-deps
74+
}, [codeValue, size, gs1Mode, config.shape, config.margin, config.logLevel]);
75+
76+
if (error || !svg) {
77+
return (
78+
<div className="barcode-renderer">
79+
{error && config.logLevel !== "None" && (
80+
<div className="alert alert-danger" role="alert">
81+
<strong>Unable to generate Data Matrix.</strong> Please check the value and format
82+
configuration.
83+
</div>
84+
)}
85+
</div>
86+
);
87+
}
88+
89+
const getSvgElement = (): SVGSVGElement | null => containerRef.current?.querySelector("svg") ?? null;
90+
91+
const button = downloadButton && (
92+
<DownloadButton
93+
onClick={() => downloadCode({ current: getSvgElement() }, config, downloadButton.fileName)}
94+
ariaLabel={downloadButton.label}
95+
caption={downloadButton.caption}
96+
/>
97+
);
98+
99+
return (
100+
<div className="barcode-renderer datamatrix-renderer">
101+
{buttonPosition === "top" && button}
102+
<div ref={containerRef} className="datamatrix-svg" dangerouslySetInnerHTML={{ __html: svg }} />
103+
{buttonPosition === "bottom" && button}
104+
</div>
105+
);
106+
}

packages/pluggableWidgets/barcode-generator-web/src/utils/download-code.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export async function downloadCode(
2525
// Process overlay images for QR codes
2626
if (config.type === "qrcode") {
2727
await processQRImages(clonedSvg);
28+
} else if (config.type === "datamatrix") {
29+
fileNamePrefix = config.gs1Mode ? "datamatrix_gs1" : "datamatrix";
2830
} else {
2931
fileNamePrefix = `${config.type}_${config.format}`;
3032
}

0 commit comments

Comments
 (0)