Repository navigation
Feat/zpl first barcode sizing - #39
Merged
Merged
Conversation
Phase 1 of the ZPL-first barcode sizing audit. The mechanism the upcoming work was meant to add already exists — getDisplaySize maps each barcode type from the bwip-js intrinsic canvas size to the ZPL-correct display pixels per Zebra-spec module formulas. Document that policy at the module level, audit each symbology's status: - 18+ types are fully ZPL-correct (modules × moduleWidth match Zebra) - planet/postal trade visual bar fidelity for ZPL-correct width - code93/code11/plessey deliberately stay bwip-natural (algorithm differences would distort bars worse than a slight quiet-zone gap) Add a static-parse coverage test so any future barcode type added to BCID without a matching case in getUprightDisplaySize fails CI instead of silently falling through to the default and returning bwip-natural pixels (a ZPL-first violation).
… in bbox Phase 2(A,B) of the ZPL-first sizing audit. The displayed bbox for EAN/UPC barcodes is now barH + 13 dots and for LOGMARS barH + 20 dots, matching what Zebra firmware actually reserves on the print regardless of printInterpretation. The bwip-js bitmap (which only renders the bars) gets stretched vertically to fill the larger bbox — that visual distortion is an accepted intermediate compromise until the renderer is split to draw the bitmap at bar height inside a taller bbox. - New LOGMARS_TEXT_ZONE_DOTS constant (20) - getDisplaySize includes both zones in the height - labelarySync strict bounds check now applies to EAN/UPC/LOGMARS without subtraction; height is checked for LOGMARS too - visualRegression failingTests gets the affected fixtures with a pointer to the Phase-3 follow-up GS1 Databar stacked variants (Phase 2C) are still tracked as a separate gap — height divergence per symbology needs per-fixture investigation.
…nvas size Phase 2(C) of the ZPL-first sizing audit. bwip-js renders all non-omni GS1 DataBar variants at the same canvas height as Omnidirectional (33 modules) regardless of symbology, while Zebra firmware uses the per-symbology spec heights: Omni 33, Truncated 13, Stacked 14, Stacked Omni 72, Limited 10, Expanded 34 (per GS1 General Specifications) getDisplaySize now consults a GS1_DATABAR_SPEC_HEIGHT_MODULES map for the height instead of dividing the bwip canvas height — so the bbox matches the printed footprint for variants 1-6 (sym 7 Expanded Stacked is segments-dependent and still falls back to bwip-natural). The labelarySync strict bounds check now passes for variants 1-6 too; only sym 7 stays excluded. visualRegression keeps all variants skipped because the bwip bitmap rendering still differs in shape — fixing the visual diff is Phase-3 work (split bitmap from bbox).
…stretched Phase 3 of the ZPL-first sizing audit. After Phase 2 the bbox includes the firmware-reserved text zone, but the bitmap was stretched to fill the bbox so the bars looked taller than the printed output. Split the two: - getDisplaySize now returns barH and barTopPx alongside the full w/h, describing where the bars sit inside the bbox. - BarcodeObject draws the bwip-js bitmap at barH (not the full h) so the bars render at their true height. The text zone is empty whitespace inside the bbox. - The default-path KImage gets wrapped in a Group with an invisible full-bbox Rect so the selection footprint still matches what the printer reserves, even when the bitmap is shorter. - visualRegression test draws the bitmap at the bar sub-rect too, so the upright EAN/UPC and LOGMARS fixtures match Labelary again and can be removed from the failingTests skip list. Rotated EAN/UPC stays skipped — barH/barTopPx are not yet rotation-aware, tracked as a follow-up.
The text zone reserved by Labelary travels around the bbox as the symbol rotates: bottom for N, left for R, top for I, right for B. Previous Phase 3 only handled N. Extend BarcodeDisplaySize with barW + barLeftPx so the bar sub-rect can be on any side, and shift the Konva Group origin by (-barLeftPx, -barTopPx) so the rendered bars stay anchored at FO while the bbox extends in the text-zone direction. Drag-end recovers the saved obj.x/y by adding back barLeftPx/barTopPx in pixels. The FT baseline correction now subtracts barH instead of displayH, since the FT origin is the bar bottom, not the bbox bottom (this also fixes a regression Phase 2 introduced for FT EAN/LOGMARS). The visual regression test draws the bitmap at FO directly — the bbox shift is a Konva rendering concern, not a pixel-position one. With this change the rotated EAN/UPC fixtures pass strict-bounds and pixel-match, no remaining failingTests skips for these types.
…behavior Sym 7 (Expanded Stacked) cannot be cross-validated against Labelary: bwip-js requires the (AI)data parens-AI input format that Zebra ^BR sym 7 silently rejects and renders empty for. Neither side can produce a shared ground truth, so the implementation falls back to the bwip-natural canvas height — what the user sees in bwip's preview is what they get in the bbox. Document the limitation in the gs1databar case comment and add a unit test pinning the bwip-natural height formula so any future change requires an explicit decision.
…ext branches Smell found in self-validation: the showText branch (LOGMARS / Code128 with HRI) and showRotatedText branch had no full-bbox guard. Their HRI Text node uses getSelfRect=()=>0 to keep resize anchored at the bars, so the Group's auto-bbox shrank to the bar sub-rectangle (barH x barW) instead of spanning the firmware-reserved footprint (displayH x displayW). Selection-handles missed the text-zone reservation; only the default-path Group had this guard. Add an invisible full-bbox Rect to both branches, mirroring the default path. The Rect is non-listening so it doesn't intercept clicks but contributes to getClientRect, restoring the ZPL-correct selection footprint.
Smell #3 from validation: extending the text-zone detection (e.g. for a future barcode type with reserved whitespace) meant adding another branch to a growing if/else chain. Replace with a Partial<Record> keyed by LabelObject type, letting new entries land as one-line additions to the table.
…bles Smells #2 and #4 from validation: - The FT/FO Y-shift logic was duplicated forward in the render path and inverted in the drag-end handler — same per-type branches in both, easy to drift apart. Compute the shift once via an IIFE (ftYShiftDots) and a foYShiftDots constant, then apply forward in render and inverted in drag-end. Render and inverse stay in lockstep by construction. - The component held six separate top-level variables (displayW, displayH, barW, barH, barLeftPx, barTopPx) that re-aliased the fields of the BarcodeDisplaySize struct. Consolidate into a single so callers reference dim.w / dim.barW etc. No more accidental shadowing of bbox vs bar dims.
Re-validation found that EAN/UPC printInterp digits and showRotatedText/showText fallbacks computed text Y from `h` (full bbox including the firmware-reserved text zone) after the Phase 2 bbox extension. The visual digits drifted below the text zone instead of sitting inside it where Labelary draws them. Switch every "text below bars" position from h to bh (= barH). For symbologies without a text zone bh equals h so the math is unchanged; for EAN/UPC and LOGMARS the digits/HRI now sit just below the bars, matching the printed layout. Untested by fixtures (printInterpretation:true exists only for LOGMARS upright, which uses the text-above branch); manual verification recommended in the next browser pass.
User-reported visual mismatch in the designer: - code93/code11 displayed too short (bwip's narrower quiet zone) - plessey displayed too long (different bar encoding) Both were previously listed as 'bwip-natural width returned' in the audit and skipped from labelarySync's strict bounds check. Following the user's direction (ZPL-Treue first, accept visual stretching as fallback), correct the displayed bbox to match Zebra firmware: - code93: add 17 modules to bwip canvas count (constant quiet-zone shortfall, content-independent) - code11: add 19 modules (same reason, slightly different delta) - plessey: multiply bwip width by 49/82 (≈0.598, derived from the canonical '12345678' fixture, content-proportional) The bitmap inside still renders bwip's bar pattern at its natural size, so it stretches slightly (~10-25% wider for code93/code11) or squeezes (~40% narrower for plessey) to fill the corrected bbox. Tracked as a known visual-fidelity limitation; the next commit will add a UI hint for affected types. The hasBwipSizeMismatch skip in labelarySync.test.ts is removed — all symbologies now pass strict bounds.
User-prompted: when a symbology renders with visual stretching (e.g. code93/code11/plessey after the bbox-correction fix), surface that fact without screaming "the designer is wrong". Tiny info icon next to the type label in the Properties header — muted opacity, only visible if the user looks at the panel — with a native title tooltip carrying the localised explanation. - Add BWIP_VISUAL_APPROX_TYPES set in bwipConstants - Add properties.visualApproxHint locale key in all 32 languages via the add_locale_key script - PropertiesPanel renders the hint icon when the selected type is in the set
User-reported: GS1 Databar's image inside the bbox is a few pixels shorter at top and bottom than what Labelary prints. Cause: bwip-js opts include paddingheight=2 which adds whitespace rows on top and bottom of the canvas; when we then draw that bitmap at the spec-correct bbox height, the bars fill proportionally less than the firmware-reserved space. Add an optional bitmapCrop field to BarcodeDisplaySize describing which sub-rect of the source bitmap the renderer should use, and populate it for gs1databar with the padding rows excluded (2 × bwipScale per side, derived from the paddingheight option). BarcodeObject's KImage forwards crop to Konva, the visualRegression test uses the 9-arg drawImage form for the same effect. Other symbologies pass undefined and use the full bitmap as before.
User observation: even with the bbox dimensions and bitmap padding fixed, the GS1 DataBar bar pattern doesn't match Labelary 100% — bwip-js and Zebra firmware encode the same GS1 data with the same total module count but place the bar/space transitions at different positions. Reimplementing the encoder is out of scope. Add gs1databar to BWIP_VISUAL_APPROX_TYPES so the Properties hint icon surfaces for it too, and extend the docstring to note that the limitation here is encoder-divergence, not stretching.
…t GS1 input AI 01 followed by exactly 11 numeric digits is not a valid GTIN-14 element string. Zebra firmware emits General Compaction (~149 modules at 8dpmm) rather than Method 1 padding. bwip-js with (01)<padded GTIN> would force Method 1 (~133 modules), producing a 16-module bbox shortfall vs print. Route this specific input class through (99) so bwip uses General Compaction too. Empirical cutoff (probed against Labelary). The fallback lives in bwipHelpers.ts next to the call site so wrapGs1AIs stays a pure GS1 domain helper.
There was a problem hiding this comment.
Code Review
This pull request implements a "ZPL-first" sizing policy for barcodes to ensure that the designer's bounding boxes accurately reflect Zebra firmware print outputs. Key changes include a refactored BarcodeObject component that uses a new BarcodeDisplaySize interface to manage bar sub-rectangles and reserved text zones, and updated logic in bwipHelpers.ts to handle symbology-specific offsets and scaling for EAN, UPC, LOGMARS, and GS1 DataBar. Review feedback suggests sanitizing GS1 content strings to improve the robustness of fragment detection and adding a default fallback for moduleWidth to prevent potential NaN values in sizing calculations.
App tsconfig kept node types out of scope but tests need them — pre-existing `tsc -b` failure on test files importing `node:fs` etc. App config now excludes `*.test.ts` and `src/test`, and a standalone tsconfig.test.json adds node types for the test sources. Build pipeline runs both.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.