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