1212 * bwipHelpers.test.ts ensures every BCID-registered type has a case.
1313 */
1414
15+ import bwipjs from "bwip-js/browser" ;
1516import { ObjectRegistry , type LeafObject } from "../../registry" ;
1617import type { LabelObject } from "../../types/Group" ;
1718import type { Gs1DatabarProps } from "../../registry/gs1databar" ;
@@ -92,6 +93,7 @@ const BCID: Partial<Record<LabelObject["type"], string>> = {
9293 qrcode : "qrcode" ,
9394 datamatrix : "datamatrix" ,
9495 aztec : "azteccodecompact" ,
96+ maxicode : "maxicode" ,
9597 micropdf417 : "micropdf417" ,
9698 codablock : "codablockf" ,
9799 // Placeholder — actual bcid (ean2 vs ean5) is resolved from the
@@ -103,6 +105,48 @@ const BCID: Partial<Record<LabelObject["type"], string>> = {
103105export const BWIP_SCALE = 2 ;
104106const BWIP_2D_INTERNAL_SCALE = 2 ;
105107
108+ /** Reused unattached canvas for dry-run encoding validators (e.g.
109+ * `validateMaxicodeBwip`). Cheaper than creating a fresh element
110+ * per call and we don't need its pixel output, just the success
111+ * /throw signal from bwip-js. Initialised lazily so SSR-style
112+ * imports don't break — `document` exists only in the browser. */
113+ let _validationCanvas : HTMLCanvasElement | null = null ;
114+ function getValidationCanvas ( ) : HTMLCanvasElement {
115+ if ( ! _validationCanvas ) _validationCanvas = document . createElement ( "canvas" ) ;
116+ return _validationCanvas ;
117+ }
118+
119+ /** Strip the `bwip-js: bwipp.symbology:` prefixes that bwip-js
120+ * decorates every encoder error with, so the bare diagnostic
121+ * ("Expected postcode followed by group separator character",
122+ * etc.) can be shown as-is in the UI. Shared between the canvas
123+ * render (BarcodeObject) and dry-run validators below. */
124+ export function cleanBwipError ( e : unknown ) : string {
125+ const raw = e instanceof Error ? e . message : String ( e ) ;
126+ return raw . replace ( / ^ b w i p - j s : \s * / i, "" ) . replace ( / ^ b w i p p \. [ ^ : ] + : \s * / i, "" ) ;
127+ }
128+
129+ /** Dry-run a Maxicode encode against the shared validation canvas
130+ * to check whether the (content, mode) combination would succeed
131+ * in the real render path. Returns null on success or the cleaned
132+ * bwip-js error message on failure (e.g. SCM-format mismatch for
133+ * modes 2/3). Uses the same option shape `buildBwipOptions`
134+ * produces so canvas render and panel-side validation can't drift. */
135+ export function validateMaxicodeBwip ( content : string , mode : number ) : string | null {
136+ try {
137+ const opts = {
138+ bcid : "maxicode" ,
139+ text : content || " " ,
140+ scale : BWIP_SCALE ,
141+ mode,
142+ } as unknown as Parameters < typeof bwipjs . toCanvas > [ 1 ] ;
143+ bwipjs . toCanvas ( getValidationCanvas ( ) , opts ) ;
144+ return null ;
145+ } catch ( e ) {
146+ return cleanBwipError ( e ) ;
147+ }
148+ }
149+
106150/**
107151 * Estimate the number of columns ZPL/Labelary would choose for PDF417 when
108152 * columns=0 (auto). Zebra uses a heuristic to keep the symbol somewhat square.
@@ -506,6 +550,14 @@ export function buildBwipOptions(
506550 opts = { bcid, text : p . content || " " , scale : BWIP_SCALE } ;
507551 break ;
508552 }
553+ case "maxicode" : {
554+ const p = obj . props ;
555+ // bwip-js maxicode uses `mode` (number) directly; modes 2/3
556+ // would also accept a structured carrier message via `parse`,
557+ // but we don't expose SCM editing yet so pass content as-is.
558+ opts = { bcid, text : p . content || " " , scale : BWIP_SCALE , mode : p . mode } ;
559+ break ;
560+ }
509561 case "micropdf417" : {
510562 const p = obj . props ;
511563 opts = {
@@ -902,6 +954,15 @@ function getUprightDisplaySize(
902954 ( cw / ( BWIP_SCALE * BWIP_2D_INTERNAL_SCALE ) ) * modulePx ;
903955 return { w : size , h : size } ;
904956 }
957+ case "maxicode" : {
958+ // Fixed physical size — no module-magnification prop. bwip-js
959+ // emits the canonical maxicode bitmap at BWIP_SCALE internal
960+ // pixels per dot, so converting cw/ch back to dots and then
961+ // to stage px places the symbol at its true printed size.
962+ const w = dotsToPx ( cw / BWIP_SCALE , scale , dpmm ) ;
963+ const h = dotsToPx ( ch / BWIP_SCALE , scale , dpmm ) ;
964+ return { w, h } ;
965+ }
905966 case "micropdf417" : {
906967 const p = obj . props ;
907968 // bwip-js ignores rowheight for micropdf417 and always uses 2 internal pixels per row.
0 commit comments