From e1d27b44a7e83ee926f7f2089e60b62b51caa9d3 Mon Sep 17 00:00:00 2001 From: seb Date: Fri, 30 Oct 2026 07:00:33 +0100 Subject: [PATCH] u --- package.json | 1 - src/services/manifestPdf.js | 64 +++++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 00be6db..ec878f4 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ }, "allowScripts": { "github:sebseb7/react-canvas-label-editor#7f0e31b66ff22da011e872b201017398b14e9dd9": true, - "canvas@3.2.3": true, "better-sqlite3@13.0.3": true }, "overrides": { diff --git a/src/services/manifestPdf.js b/src/services/manifestPdf.js index 4e52dd0..2449024 100644 --- a/src/services/manifestPdf.js +++ b/src/services/manifestPdf.js @@ -1,5 +1,4 @@ import PDFDocument from 'pdfkit'; -import { createCanvas } from 'canvas'; import JsBarcode from 'jsbarcode'; import { optimizeImage } from 'wasm-image-optimization'; import { getStore } from '../db/store.js'; @@ -91,31 +90,26 @@ const COL_DELIV = { }; /** - * Generates a PNG barcode buffer for embedding in PDFDocument. - * Reuses a single canvas instance on format fallback. + * Computes barcode binary bit patterns using JsBarcode's object renderer. + * Zero native dependencies (pure JS). */ -export function generateBarcodePng(text, preferredFormat = 'CODE128', options = {}) { +export function getBarcodeData(text, preferredFormat = 'CODE128') { const str = text ? String(text).trim() : ''; if (!str) return null; - const width = options.width || 120; - const height = options.height || 36; - const canvas = createCanvas(width, height); - const baseOpts = { - displayValue: options.displayValue ?? true, - fontSize: options.fontSize || 9, - textMargin: options.textMargin || 1, - margin: options.margin || 2, + const encode = (format) => { + const target = {}; + JsBarcode(target, str, { format, displayValue: false }); + const binary = target.encodings.map((e) => e.data).join(''); + return binary ? { binary, totalModules: binary.length } : null; }; try { - JsBarcode(canvas, str, { ...baseOpts, format: preferredFormat }); - return canvas.toBuffer('image/png'); + return encode(preferredFormat); } catch { if (preferredFormat !== 'CODE128') { try { - JsBarcode(canvas, str, { ...baseOpts, format: 'CODE128' }); - return canvas.toBuffer('image/png'); + return encode('CODE128'); } catch { return null; } @@ -124,6 +118,36 @@ export function generateBarcodePng(text, preferredFormat = 'CODE128', options = } } +/** + * Draws the barcode onto the PDFKit document as crisp vector rectangles. + */ +export function drawBarcode(doc, barcode, x, y, width, height, color = PALETTE.primary) { + if (!barcode?.binary || !width || !height) return; + const { binary, totalModules } = barcode; + if (!totalModules) return; + + const moduleWidth = width / totalModules; + + doc.save(); + doc.fillColor(color); + + let run = 0; + for (let i = 0; i < binary.length; i++) { + if (binary[i] === '1') { + run++; + } else if (run > 0) { + doc.rect(x + (i - run) * moduleWidth, y, run * moduleWidth, height); + run = 0; + } + } + if (run > 0) { + doc.rect(x + (binary.length - run) * moduleWidth, y, run * moduleWidth, height); + } + + doc.fill(); + doc.restore(); +} + async function resolveThumbPng(store, googleAuthId, picHash, smallthumbbinary) { if (smallthumbbinary) { try { @@ -470,14 +494,14 @@ export function generateManifestPdf({ doc.font('Helvetica-Bold').fontSize(7).fillColor(PALETTE.primary) .text(line.ownSku || '–', COL_DELIV.ownSku, y + 2, { width: COL_DELIV.ownSkuW, ellipsis: true }); if (line.ownSkuBarcode) { - try { doc.image(line.ownSkuBarcode, COL_DELIV.ownSku, centerY, { width: 88, height: 20 }); } catch { /* ignore */ } + try { drawBarcode(doc, line.ownSkuBarcode, COL_DELIV.ownSku, centerY, 88, 20); } catch { /* ignore */ } } if (line.ean) { doc.font('Helvetica').fontSize(6.5).fillColor(PALETTE.muted) .text(line.ean, COL_DELIV.ean, y + 2, { width: COL_DELIV.eanW, ellipsis: true }); if (line.eanBarcode) { - try { doc.image(line.eanBarcode, COL_DELIV.ean, centerY, { width: 76, height: 20 }); } catch { /* ignore */ } + try { drawBarcode(doc, line.eanBarcode, COL_DELIV.ean, centerY, 76, 20); } catch { /* ignore */ } } } else { doc.font('Helvetica').fontSize(8).fillColor(PALETTE.muted) @@ -798,8 +822,8 @@ async function enrichDeliveryLines(store, googleAuthId, companyId, partnerId, li ]); const currentStock = trackStock ? Number(stockRes?.rows?.[0]?.stock ?? 0) : null; - const ownSkuBarcode = ownSku ? generateBarcodePng(ownSku, 'CODE128', { width: 110, height: 26, displayValue: false }) : null; - const eanBarcode = ean ? generateBarcodePng(ean, 'EAN13', { width: 100, height: 26, displayValue: false }) : null; + const ownSkuBarcode = ownSku ? getBarcodeData(ownSku, 'CODE128') : null; + const eanBarcode = ean ? getBarcodeData(ean, 'EAN13') : null; return { ownSku,