From 371333802a801c5e8f705632e2e961e274a39e2b Mon Sep 17 00:00:00 2001 From: Knee Cola Date: Sat, 22 Nov 2025 14:22:57 +0100 Subject: [PATCH] Add PDF417 barcode rendering component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements client-side PDF417 barcode rendering with React component. Uses useEffect to prevent hydration mismatch by generating barcodes only after component mount. Integrates barcode display in location cards. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/lib/pdf/renderBarcode.ts | 51 ++++++++++++++++++++++++++++++++++++ app/ui/Pdf417Barcode.tsx | 50 +++++++++++++++++++++++++++++++++++ app/ui/ViewLocationCard.tsx | 2 ++ 3 files changed, 103 insertions(+) create mode 100644 app/lib/pdf/renderBarcode.ts create mode 100644 app/ui/Pdf417Barcode.tsx diff --git a/app/lib/pdf/renderBarcode.ts b/app/lib/pdf/renderBarcode.ts new file mode 100644 index 0000000..f614fd6 --- /dev/null +++ b/app/lib/pdf/renderBarcode.ts @@ -0,0 +1,51 @@ +import { BarcodeArray } from './pdf417'; + +/** + * Renders a PDF417 barcode matrix to a canvas and returns it as a data URL. + * + * This function creates an HTML canvas element, draws the barcode by iterating through + * the barcode matrix, and converts the canvas to a base64-encoded PNG data URL that + * can be used as an image source. + * + * @param barcodeMatrix - The barcode array generated by the PDF417 encoder containing + * the barcode matrix data with dimensions and binary code values + * @param blockWidth - The width in pixels of each individual barcode module (bar/space unit) + * @param blockHeight - The height in pixels of each individual barcode module (bar/space unit) + * + * @returns A data URL string (base64-encoded PNG) representing the rendered barcode image, + * suitable for use in an HTML img src attribute + * + * @example + * ```typescript + * const pdf417 = createPDF417(); + * pdf417.init("Hello World", 2, 2); + * const barcodeArray = pdf417.getBarcodeArray(); + * const dataUrl = renderBarcode(barcodeArray, 2, 4); + * // dataUrl can now be used: + * ``` + */ +export function renderBarcode(barcodeMatrix: BarcodeArray, blockWidth: number, blockHeight: number) { + + const canvas = document.createElement('canvas'); + canvas.width = barcodeMatrix.num_cols * blockWidth; + canvas.height = barcodeMatrix.num_rows * blockHeight; + const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; + + let positionY = 0; + for (let row = 0; row < barcodeMatrix.num_rows; row += 1) { + let positionX = 0; + + for (let col = 0; col < barcodeMatrix.num_cols; col += 1) { + if (barcodeMatrix.bcode[row][col] === 1) { + ctx.fillStyle = '#000'; + } else { + ctx.fillStyle = '#FFF'; + } + ctx.fillRect(positionX, positionY, blockWidth, blockHeight); + positionX += blockWidth; + } + positionY += blockHeight; + } + + return canvas.toDataURL(); +} \ No newline at end of file diff --git a/app/ui/Pdf417Barcode.tsx b/app/ui/Pdf417Barcode.tsx new file mode 100644 index 0000000..cb087f0 --- /dev/null +++ b/app/ui/Pdf417Barcode.tsx @@ -0,0 +1,50 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { generateBarcode } from '../lib/pdf/pdf417'; +import { renderBarcode } from '../lib/pdf/renderBarcode'; +import { EncodePayment } from 'hub-3a-payment-encoder'; + +export const Pdf417Barcode = () => { + const [bitmapData, setBitmapData] = useState(undefined); + + useEffect(() => { + + const paymentParams = { + Iznos:"123,66", // NOTE: use comma, not period! + ImePlatitelja:"Ivan Horvat", + AdresaPlatitelja:"Ilica 23", + SjedistePlatitelja:"10000 Zagreb", + Primatelj:"VODOOPSKRBA I ODV. D.O.O.", + AdresaPrimatelja:"FOLNEGOVIĆEVA 1", + SjedistePrimatelja:"ZAGREB", + IBAN:"HR8924020061100679445", + ModelPlacanja: "HR00", // MUST contain "HR" prefix! + PozivNaBroj:"2025-05", + SifraNamjene:"", + OpisPlacanja:"Budakova Režije", + }; + + + const hub3a_text = EncodePayment(paymentParams); + + const barcodeMatrix = generateBarcode(hub3a_text); + const bitmap = renderBarcode(barcodeMatrix, 2, 2); + setBitmapData(bitmap); + }, []); + + // Don't render until bitmap is generated (prevents hydration mismatch) + if (!bitmapData) { + return ( +
+ +
+ ); + } + + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/app/ui/ViewLocationCard.tsx b/app/ui/ViewLocationCard.tsx index 021db63..8bc6c18 100644 --- a/app/ui/ViewLocationCard.tsx +++ b/app/ui/ViewLocationCard.tsx @@ -6,6 +6,7 @@ import { formatYearMonth } from "../lib/format"; import { formatCurrency } from "../lib/formatStrings"; import { useTranslations } from "next-intl"; import { ViewBillBadge } from "./ViewBillBadge"; +import { Pdf417Barcode } from "./Pdf417Barcode"; export interface ViewLocationCardProps { location: BillingLocation @@ -34,6 +35,7 @@ export const ViewLocationCard:FC = ({location: { _id, nam

: null } + ); }; \ No newline at end of file