Repository navigation
feat(barcode): EAN/UPC extended guard tails via bwip-raw canvas - #142
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces native EAN/UPC barcode rendering via a raw canvas to support extending guard tails when Human Readable Interpretation (HRI) is enabled, matching Zebra firmware behavior. It also refactors cross-type property reads and adjusts selection bounding boxes and strokes for EAN/UPC barcodes. The review feedback highlights two critical issues: first, when interpretation is disabled, the canvas is vertically stretched and blurred because the canvas height does not match the image height; second, sub-pixel rendering can cause anti-aliasing blurriness. The reviewer suggests passing the full text zone height along with a boolean flag to control guard tail extension, rounding pixel heights to integers, and adding defensive checks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const eanHeight = (obj.props as { height: number }).height; | ||
| const modulePxInt = get1DBwipScale(moduleWidth, scale, dpmm); | ||
| const barHeightPx = dotsToPx(eanHeight, scale, dpmm); | ||
| const tailHeightPx = printInterpEnabled | ||
| ? dotsToPx(EAN_TEXT_ZONE_DOTS, scale, dpmm) | ||
| : 0; | ||
| barcodeCanvas = renderEanUpcRawCanvas( | ||
| obj.type as EanUpcType, | ||
| rawContent, | ||
| modulePxInt, | ||
| barHeightPx, | ||
| tailHeightPx, | ||
| ); |
There was a problem hiding this comment.
When printInterpretation is disabled (printInterpEnabled is false), the EAN/UPC layout still reserves the 13-dot text zone at the bottom of the barcode to match the firmware-reserved footprint. However, passing tailHeightPx = 0 to renderEanUpcRawCanvas results in a canvas height of only barHeightPx. Since the KImage height is unconditionally set to ub.barH + EAN_TEXT_ZONE_DOTS, the canvas image gets vertically stretched by ~13%, causing blurry rendering and overlapping the reserved text zone.
To fix this, we should always pass the full EAN_TEXT_ZONE_DOTS height as tailHeightPx to ensure the canvas height matches the KImage height, and pass printInterpEnabled as a boolean flag to control whether the guard tails actually extend into this zone.
const eanHeight = (obj.props as { height: number }).height;
const modulePxInt = get1DBwipScale(moduleWidth, scale, dpmm);
const barHeightPx = dotsToPx(eanHeight, scale, dpmm);
const tailHeightPx = dotsToPx(EAN_TEXT_ZONE_DOTS, scale, dpmm);
barcodeCanvas = renderEanUpcRawCanvas(
obj.type as EanUpcType,
rawContent,
modulePxInt,
barHeightPx,
tailHeightPx,
printInterpEnabled,
);
| export function renderEanUpcRawCanvas( | ||
| type: EanUpcType, | ||
| text: string, | ||
| modulePxInt: number, | ||
| barHeightPx: number, | ||
| tailHeightPx: number, | ||
| ): HTMLCanvasElement | null { | ||
| if (modulePxInt <= 0 || barHeightPx <= 0) return null; | ||
| let stack: BwipRawLinear[]; | ||
| try { | ||
| stack = bwipjs.raw({ bcid: type, text, includetext: true } as never) as BwipRawLinear[]; | ||
| } catch { | ||
| return null; | ||
| } | ||
| const g = stack?.[0]; | ||
| if (!g?.sbs) return null; | ||
| let totalModules = 0; | ||
| for (const w of g.sbs) totalModules += w; | ||
| const canvas = document.createElement("canvas"); | ||
| canvas.width = totalModules * modulePxInt; | ||
| canvas.height = barHeightPx + Math.max(0, tailHeightPx); | ||
| const ctx = canvas.getContext("2d"); | ||
| if (!ctx) return null; | ||
| ctx.fillStyle = "#000000"; | ||
| let cx = 0; | ||
| let isBar = true; | ||
| let barIdx = 0; | ||
| for (const w of g.sbs) { | ||
| const wPx = w * modulePxInt; | ||
| if (isBar) { | ||
| const isGuard = (g.bbs?.[barIdx] ?? 0) < 0; | ||
| const h = isGuard ? canvas.height : barHeightPx; | ||
| ctx.fillRect(cx, 0, wPx + RAW_BAR_SEAM, h); | ||
| barIdx++; | ||
| } | ||
| cx += wPx; | ||
| isBar = !isBar; | ||
| } | ||
| return canvas; | ||
| } |
There was a problem hiding this comment.
Update renderEanUpcRawCanvas to accept the extendGuards boolean flag to control guard tail extension. Additionally, round barHeightPx and tailHeightPx to integers to prevent sub-pixel rendering (anti-aliasing) which causes blurry barcode edges and scanning failures. Also, add a defensive check to ensure totalModules is greater than 0 before creating the canvas.
export function renderEanUpcRawCanvas(
type: EanUpcType,
text: string,
modulePxInt: number,
barHeightPx: number,
tailHeightPx: number,
extendGuards: boolean,
): HTMLCanvasElement | null {
const barH = Math.round(barHeightPx);
const tailH = Math.round(tailHeightPx);
if (modulePxInt <= 0 || barH <= 0) return null;
let stack: BwipRawLinear[];
try {
stack = bwipjs.raw({ bcid: type, text, includetext: true } as never) as BwipRawLinear[];
} catch {
return null;
}
const g = stack?.[0];
if (!g?.sbs) return null;
let totalModules = 0;
for (const w of g.sbs) totalModules += w;
if (totalModules <= 0) return null;
const canvas = document.createElement("canvas");
canvas.width = totalModules * modulePxInt;
canvas.height = barH + Math.max(0, tailH);
const ctx = canvas.getContext("2d");
if (!ctx) return null;
ctx.fillStyle = "#000000";
let cx = 0;
let isBar = true;
let barIdx = 0;
for (const w of g.sbs) {
const wPx = w * modulePxInt;
if (isBar) {
const isGuard = extendGuards && (g.bbs?.[barIdx] ?? 0) < 0;
const h = isGuard ? canvas.height : barH;
ctx.fillRect(cx, 0, wPx + RAW_BAR_SEAM, h);
barIdx++;
}
cx += wPx;
isBar = !isBar;
}
return canvas;
}
No description provided.