Skip to content
Draft
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
2 changes: 2 additions & 0 deletions openspec/changes/add-datamatrix-generation/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-09
51 changes: 51 additions & 0 deletions openspec/changes/add-datamatrix-generation/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## Context

`@mendix/barcode-generator-web` renders three-way today: 1D barcodes via `jsbarcode` (isolated in `src/utils/barcodeRenderer-utils.ts`) and QR via `qrcode.react` (isolated in `src/components/QRCode.tsx`). `src/config/Barcode.config.ts` maps Mendix props into a lib-agnostic discriminated union `BarcodeConfig = BarcodeTypeConfig | QRCodeTypeConfig`, and `src/BarcodeGenerator.tsx:25` dispatches on `config.type`. Rendering is SVG-first; download converts SVG→PNG via `src/utils/download-code.ts` + `download-utils.ts`.

Neither current lib can produce DataMatrix. Pharma requires **GS1 DataMatrix** (FNC1 + GS1 Application Identifiers), not just plain DataMatrix.

## Goals / Non-Goals

**Goals:**

- Add DataMatrix and GS1 DataMatrix generation as a third render path, reusing the existing config-union + dispatch + SVG→PNG download architecture.
- Keep the new library isolated behind a single seam module, mirroring the QR split.
- No regression to existing barcode/QR behavior.

**Non-Goals:**

- Replacing jsbarcode or qrcode.react.
- Building a GS1 AI parser/validator beyond loose syntax checks (bwip-js validates the encoding).
- Adding non-DataMatrix 2D symbologies (Aztec, PDF417) — out of scope.

## Decisions

**Library: `@bwip-js/browser`.** Only maintained (v4.11.x, ~676k dl/wk, MIT) library with native GS1 DataMatrix (`bcid: "gs1datamatrix"`, accepts human-readable AI syntax) and rectangular support. Import the named `datamatrix` + `drawingSVG` exports so bundlers tree-shake — do NOT use `toSVG()`, which links all ~100 BWIPP encoders.

- _Alternatives:_ `datamatrix-svg` (no GS1, unmaintained since 2020) rejected; `@zxing/library` (used by scanner) is decode-only, cannot encode.

**Third render path, not a `customCodeFormat` sub-option.** DataMatrix is 2D with its own options (GS1 toggle, shape), like QR. Add `DataMatrix` to the top-level `codeFormat` enum and a `DataMatrixTypeConfig` (`type: "datamatrix"`) to the union. `barcodeConfig()` branches on `format === "DataMatrix"` before the QR check.

- _Alternative:_ nesting under `customCodeFormat` (the 1D list) rejected — that list is jsbarcode-specific and 1D-shaped.

**GS1 as a boolean toggle under Data Matrix, not a separate top-level format.** Matches the pharma mental model ("same symbology, GS1-encoded") and keeps the top-level enum small. `dmGs1Mode` selects `gs1datamatrix` vs `datamatrix`.

**New seam `src/components/DataMatrix.tsx`.** Mirrors `QRCode.tsx`: builds bwip-js options, renders SVG, exposes an `SVGSVGElement` ref so the existing `DownloadButton` + `downloadCode(ref, config, ...)` pipeline works unchanged. Validation + try/catch error state live in this component (the `useRenderBarcode` hook is 1D/jsbarcode-specific).

**Validation.** Extend `validateBarcodeValue` in `src/config/validation.ts` with a `DataMatrix` case (the `format` param already unions `CodeFormatEnum`): plain mode = charset/length sanity; GS1 mode = loose balanced-`(nn)` AI-syntax check. Encoder errors caught at render.

## Risks / Trade-offs

- **Bundle size** → bwip-js is large if fully linked. Mitigation: import only `datamatrix`/`drawingSVG` named exports; verify bundle delta at build time.
- **Download ref shape** → bwip-js `drawingSVG()` returns SVG markup, not a React-managed `<svg>`. Mitigation: inject markup into a container and target the real `SVGSVGElement` for the ref so SVG→PNG works.
- **GS1 AI validation drift** → hand-rolled loose check may diverge from bwip-js. Mitigation: keep it minimal (structure only), let bwip-js be the source of truth and catch its errors.
- **Two enums to keep in sync** → `codeFormat` value must round-trip through config + validation + preview. Mitigation: covered by unit tests.

## Migration Plan

Additive, no data migration. New dependency added to `package.json`; `typings/BarcodeGeneratorProps.d.ts` regenerated by build. Rollback = revert the change and drop the dependency; existing barcode/QR configs unaffected.

## Open Questions

- Exact bwip-js option for rectangular DataMatrix (shape flag vs explicit `rows`/`columns`) — confirm at implementation.
- Whether to reuse `codeMargin`/`qrSize`-style sizing or add dedicated `dmSize`/`dmMargin` props — lean toward dedicated to avoid overloading 1D "bar width" semantics.
34 changes: 34 additions & 0 deletions openspec/changes/add-datamatrix-generation/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Why

The Barcode Generator widget (`@mendix/barcode-generator-web`) can only produce 1D linear barcodes (jsbarcode) and QR codes (qrcode.react); it cannot generate DataMatrix. DataMatrix — specifically **GS1 DataMatrix** (FNC1 + GS1 Application Identifiers) — is mandatory for pharma serialization (EU FMD, US DSCSA). The Barcode Scanner widget already decodes DataMatrix, so generation closes the round-trip gap for pharma and logistics apps.

## What Changes

- Add **Data Matrix** as a top-level `codeFormat` option in the Barcode Generator.
- Support two encodings: plain DataMatrix and **GS1 DataMatrix** (pharma), selectable via a boolean toggle.
- Support square and rectangular symbol shapes.
- Add `@bwip-js/browser` as a dependency (tree-shakeable DataMatrix encoder) — the only maintained library with native GS1 DataMatrix support. jsbarcode and qrcode.react remain for their existing formats.
- Render DataMatrix as inline SVG (consistent with existing render + SVG→PNG download pipeline).
- Add runtime/design-time validation for DataMatrix values (plain charset/length; GS1 AI syntax).
- Add Studio Pro editor preview for the DataMatrix format.

No breaking changes — existing barcode/QR behavior is untouched; DataMatrix is additive.

## Capabilities

### New Capabilities

- `barcode-generation`: Generation of barcodes, QR codes, and (new) DataMatrix / GS1 DataMatrix from a string input in the Barcode Generator widget, including format selection, encoding options, validation, rendering, and download.

### Modified Capabilities

<!-- None: no prior spec exists for this widget. -->

## Impact

- **Package**: `packages/pluggableWidgets/barcode-generator-web`
- **New dependency**: `@bwip-js/browser` (MIT, tree-shakeable)
- **Widget config**: `src/BarcodeGenerator.xml` (new enum value + Data Matrix property group); regenerated `typings/BarcodeGeneratorProps.d.ts`
- **Source**: new `src/components/DataMatrix.tsx` (bwip-js seam), extended `src/config/Barcode.config.ts` (discriminated union), `src/BarcodeGenerator.tsx` (dispatch), `src/config/validation.ts`, editor-preview files
- **Reuses** existing `DownloadButton` + `src/utils/download-code.ts` SVG→PNG pipeline unchanged
- **Tests**: Jest unit tests + Playwright E2E; `CHANGELOG.md` entry
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
## ADDED Requirements

### Requirement: DataMatrix format selection

The Barcode Generator widget SHALL offer "Data Matrix" as a top-level barcode format alongside the existing Barcode (1D) and QR Code options.

#### Scenario: Data Matrix appears in format list

- **WHEN** a developer configures the Barcode Generator in Studio Pro and opens the Barcode Format property
- **THEN** "Data Matrix" is available as a selectable format value

#### Scenario: Selecting Data Matrix routes to the DataMatrix renderer

- **WHEN** the Barcode Format is set to "Data Matrix" and a non-empty value is provided
- **THEN** the widget renders a DataMatrix symbol as inline SVG, and does not invoke the 1D barcode or QR code renderers

### Requirement: Plain DataMatrix generation

The widget SHALL encode the provided string value as a standard (non-GS1) DataMatrix symbol when GS1 mode is off.

#### Scenario: Encode a plain string

- **WHEN** the format is "Data Matrix", GS1 mode is off, and the value is `ABC-12345`
- **THEN** the widget renders a scannable DataMatrix symbol encoding exactly that string

#### Scenario: Round-trip with the scanner

- **WHEN** a plain DataMatrix generated by the widget is scanned by the Barcode Scanner widget
- **THEN** the decoded value equals the original input string

### Requirement: GS1 DataMatrix generation

The widget SHALL support GS1 DataMatrix encoding (FNC1 leading character, GS1 Application Identifier data) when GS1 mode is enabled, so pharma serialization data (GTIN, expiry, batch, serial) can be encoded.

#### Scenario: Encode GS1 Application Identifier data

- **WHEN** GS1 mode is enabled and the value is `(01)09501101020917(17)261231(10)ABC123`
- **THEN** the widget renders a GS1 DataMatrix symbol with the FNC1 indicator and the AI-structured data intact

#### Scenario: GS1 mode toggles the encoder

- **WHEN** the same value is rendered first with GS1 mode off and then on
- **THEN** the off result is a plain DataMatrix and the on result is a GS1 DataMatrix (distinct symbols)

### Requirement: DataMatrix symbol shape

The widget SHALL allow choosing between square and rectangular DataMatrix symbol shapes.

#### Scenario: Rectangular shape

- **WHEN** the DataMatrix shape option is set to "rectangle"
- **THEN** the rendered symbol uses a rectangular DataMatrix layout

#### Scenario: Square shape is the default

- **WHEN** no shape is explicitly chosen
- **THEN** the widget renders a square DataMatrix symbol

### Requirement: DataMatrix value validation

The widget SHALL validate the DataMatrix value and surface errors consistently with existing formats, honoring the configured log level.

#### Scenario: Empty value at design time

- **WHEN** no value is present (design time, before dynamic binding resolves)
- **THEN** validation passes and no error is shown, matching existing barcode behavior

#### Scenario: Malformed GS1 AI syntax

- **WHEN** GS1 mode is on and the value has unbalanced or malformed Application Identifier syntax
- **THEN** the widget reports a validation error and, when the log level permits, displays the generic "unable to generate" message and logs detail to the console

#### Scenario: Encoding failure

- **WHEN** the DataMatrix encoder throws for an invalid input
- **THEN** the error is caught, the error UI is shown per log level, and the widget does not crash

### Requirement: DataMatrix download

The widget SHALL allow downloading a generated DataMatrix as a PNG using the existing download control, when downloads are enabled.

#### Scenario: Download a DataMatrix as PNG

- **WHEN** downloads are enabled and the user activates the download button on a DataMatrix
- **THEN** a PNG file of the rendered DataMatrix is downloaded, using the configured or an auto-generated filename

### Requirement: DataMatrix editor preview

The widget SHALL show a representative DataMatrix preview in the Studio Pro editor when the Data Matrix format is selected.

#### Scenario: Preview in Studio Pro

- **WHEN** the Data Matrix format is selected in the Studio Pro page editor
- **THEN** the widget preview displays a DataMatrix-style glyph rather than a 1D barcode or QR preview
41 changes: 41 additions & 0 deletions openspec/changes/add-datamatrix-generation/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## 1. Dependency & Widget Config

- [x] 1.1 Add `@bwip-js/browser` to `packages/pluggableWidgets/barcode-generator-web/package.json` dependencies
- [x] 1.2 Add `DataMatrix` enum value to top-level `codeFormat` in `src/BarcodeGenerator.xml`
- [x] 1.3 Add "Advanced Data Matrix Settings" property group in XML: `dmGs1Mode` (boolean, default false), `dmShape` (enum square/rectangle, default square), and sizing property (`dmSize` / reuse margin)
- [x] 1.4 Build to regenerate `typings/BarcodeGeneratorProps.d.ts` and confirm `CodeFormatEnum` includes `DataMatrix`

## 2. Config Model

- [x] 2.1 Add `DataMatrixTypeConfig` (`type: "datamatrix"`, `gs1Mode`, `shape`, `size`, `margin`) to the `BarcodeConfig` union in `src/config/Barcode.config.ts`
- [x] 2.2 In `barcodeConfig()`, branch `format === "DataMatrix"` before the QR check, returning the new config
- [x] 2.3 Extend `src/utils/download-code.ts` filename-prefix logic to handle `config.type === "datamatrix"`

## 3. Rendering Seam

- [x] 3.1 Create `src/components/DataMatrix.tsx` with `DataMatrixRenderer({ config })`, importing `{ datamatrix, drawingSVG }` from `@bwip-js/browser` (tree-shakeable; do NOT use `toSVG()`)
- [x] 3.2 Build bwip-js options: `bcid` = `gs1datamatrix` when `gs1Mode` else `datamatrix`; apply rectangular option when `shape === "rectangle"`; set `text`, size, margin
- [x] 3.3 Render SVG and expose a real `SVGSVGElement` ref so `DownloadButton` + `downloadCode(ref, config, ...)` work unchanged
- [x] 3.4 Wrap encoding in try/catch → error state; render the existing error-alert markup honoring `logLevel` (mirror `BarcodeRenderer`)
- [x] 3.5 Extend dispatch in `src/BarcodeGenerator.tsx:25` to route `config.type === "datamatrix"` to `DataMatrixRenderer`

## 4. Validation

- [x] 4.1 Add `DataMatrix` case to `validateBarcodeValue` in `src/config/validation.ts` (plain: charset/length sanity; GS1: loose balanced-`(nn)` AI syntax)
- [x] 4.2 Wire GS1-mode-aware validation into the DataMatrix renderer before encoding

## 5. Editor Preview

- [x] 5.1 Add a DataMatrix preview asset/branch alongside `src/hooks/useBarcodePreviewSvg.ts`, `src/assets/barcodes/*.svg`, `src/components/preview/*` so Studio Pro shows a DataMatrix glyph for the format

## 6. Tests & Changelog

- [x] 6.1 Unit tests: config mapping for DataMatrix, GS1 vs plain `bcid` selection, shape option, validation (valid GS1 AI, malformed AI, plain string, empty value)
- [ ] 6.2 Playwright E2E: render + download a DataMatrix per `docs/requirements/e2e-test-guidelines.md` _(blocked: existing e2e spec is a placeholder; needs the external Mendix testProjects page configured with a DataMatrix widget instance)_
- [x] 6.3 Add user-facing `CHANGELOG.md` entry ("Added Data Matrix and GS1 Data Matrix generation support")

## 7. Verification

- [x] 7.1 `cd packages/pluggableWidgets/barcode-generator-web && pnpm run test` passes
- [ ] 7.2 `pnpm run build` succeeds; confirm bundle delta small (bwip-js tree-shaken to DataMatrix encoder only) _(blocked: local build env has a broken rollup binary — stale `.bin/rollup` shim points to uninstalled rollup@3.29.5; unrelated to this change. Code verified via `tsc --noEmit` + jest instead.)_
- [ ] 7.3 Live Studio Pro test: plain DataMatrix scans via Barcode Scanner (round-trip); GS1 value `(01)09501101020917(17)261231(10)ABC123` renders + decodes; PNG download works; rectangular shape renders _(requires human: live Studio Pro session with `MX_PROJECT_PATH` set)_
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- Added Data Matrix and GS1 Data Matrix generation support, including square and rectangular symbol shapes.

## [1.0.0] - 2026-04-17

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"verify": "rui-verify-package-format"
},
"dependencies": {
"@bwip-js/browser": "^4.11.2",
"classnames": "^2.5.1",
"jsbarcode": "^3.12.1",
"qrcode.react": "^4.2.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { parseStyle } from "@mendix/widget-plugin-platform/preview/parse-style";
import { BarcodeGeneratorPreviewProps } from "../typings/BarcodeGeneratorProps";
import { DownloadIcon } from "./components/icons/DownloadIcon";
import { BarcodePreview } from "./components/preview/BarcodePreview";
import { DataMatrixPreview } from "./components/preview/DataMatrixPreview";
import { QRCodePreview } from "./components/preview/QRCodePreview";

const defaultDownloadCaption = "Download";
Expand All @@ -23,6 +24,7 @@ function PreviewDownloadButton(props: BarcodeGeneratorPreviewProps): ReactElemen
export function preview(props: BarcodeGeneratorPreviewProps): ReactElement {
const styles = parseStyle(props.style);
const isQrCode = props.codeFormat === "QRCode";
const isDataMatrix = props.codeFormat === "DataMatrix";
const downloadButton = <PreviewDownloadButton {...props} />;

return (
Expand All @@ -34,6 +36,8 @@ export function preview(props: BarcodeGeneratorPreviewProps): ReactElement {
>
{isQrCode ? (
<QRCodePreview {...props} downloadButton={downloadButton} />
) : isDataMatrix ? (
<DataMatrixPreview {...props} downloadButton={downloadButton} />
) : (
<BarcodePreview {...props} downloadButton={downloadButton} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import classNames from "classnames";
import { ReactElement } from "react";
import { BarcodeGeneratorContainerProps } from "../typings/BarcodeGeneratorProps";
import { BarcodeRenderer } from "./components/Barcode";
import { DataMatrixRenderer } from "./components/DataMatrix";
import { QRCodeRenderer } from "./components/QRCode";
import { barcodeConfig } from "./config/Barcode.config";

Expand All @@ -22,7 +23,13 @@ export default function BarcodeGenerator(props: BarcodeGeneratorContainerProps):
tabIndex={props.tabIndex}
style={props.style}
>
{config.type === "qrcode" ? <QRCodeRenderer config={config} /> : <BarcodeRenderer config={config} />}
{config.type === "qrcode" ? (
<QRCodeRenderer config={config} />
) : config.type === "datamatrix" ? (
<DataMatrixRenderer config={config} />
) : (
<BarcodeRenderer config={config} />
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<enumerationValues>
<enumerationValue key="CODE128">Barcode</enumerationValue>
<enumerationValue key="QRCode">QR Code</enumerationValue>
<enumerationValue key="DataMatrix">Data Matrix</enumerationValue>
<enumerationValue key="Custom">Custom</enumerationValue>
</enumerationValues>
</property>
Expand Down Expand Up @@ -135,6 +136,24 @@
<description>The size of the QR box. Note: In preview, the max height is 200px. The QR code will render at full size in your application.</description>
</property>
</propertyGroup>
<propertyGroup caption="Advanced Data Matrix Settings">
<property key="dmGs1Mode" type="boolean" defaultValue="false">
<caption>GS1 Data Matrix</caption>
<description>Encode as GS1 Data Matrix (FNC1 + Application Identifiers), e.g. (01)09501101020917(17)261231(10)ABC123. Used for pharma serialization.</description>
</property>
<property key="dmShape" type="enumeration" required="true" defaultValue="square">
<caption>Symbol shape</caption>
<description>Choose square or rectangular Data Matrix symbol shape</description>
<enumerationValues>
<enumerationValue key="square">Square</enumerationValue>
<enumerationValue key="rectangle">Rectangle</enumerationValue>
</enumerationValues>
</property>
<property key="dmSize" type="integer" required="true" defaultValue="128">
<caption>Data Matrix size</caption>
<description>The size of the Data Matrix symbol in pixels. Note: In preview, the max height is 200px. The symbol will render at full size in your application.</description>
</property>
</propertyGroup>
<propertyGroup caption="Development">
<property key="logLevel" type="enumeration" required="true" defaultValue="None">
<caption>Log Level</caption>
Expand Down
Loading
Loading