From 55b4c1a42e9f635fa03506ea7ac6272bcf6909d6 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 26 Jul 2026 21:47:21 +0100 Subject: [PATCH] Add PDF conversion utilities and update build scripts for Linux packaging Introduce a new pdfUtils.js file for on-demand PDF conversion using pdf-to-img and sharp. Update package.json to include new build scripts for Linux packaging, enhancing the build process with public package handling. Add .npmrc for public hoisting patterns and create build-linux-packages.sh for streamlined package creation. --- .npmrc | 6 ++ package.json | 11 +-- scripts/build-linux-bundle.mjs | 27 +++++++ scripts/build-linux-packages.sh | 21 ++++++ .../interfaces/receiptinterface.js | 2 +- src/pdfUtils.js | 61 ++++++++++++++++ src/utils.js | 70 ------------------- 7 files changed, 123 insertions(+), 75 deletions(-) create mode 100644 .npmrc create mode 100644 scripts/build-linux-bundle.mjs create mode 100755 scripts/build-linux-packages.sh create mode 100644 src/pdfUtils.js diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..81d4d8a --- /dev/null +++ b/.npmrc @@ -0,0 +1,6 @@ +public-hoist-pattern[]=pdf-to-img +public-hoist-pattern[]=pdfjs-dist +public-hoist-pattern[]=sharp +public-hoist-pattern[]=@img/* +public-hoist-pattern[]=canvas +public-hoist-pattern[]=@napi-rs/* diff --git a/package.json b/package.json index 6f539a8..348558d 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,10 @@ "build:electron-renderer": "vite build src/electron --outDir build/electron", "build:electron": "pnpm build && electron-builder", "build:electron:mac": "pnpm build && electron-builder --mac dmg --arm64 --x64 && electron-builder --mac pkg --arm64 && electron-builder --mac pkg --x64", - "build:linux": "pnpm run cleanBuild && pnpm run build:server && pnpm run build:linux-bundle && shx mkdir -p app_dist/linux && pkg build/pkg-entry.cjs --config package.json --targets node18-linux-x64 --output app_dist/linux/farmcontrol-server && fpm -f -s dir -t deb -n farmcontrol-server -p app_dist/linux/ -v $(node -p \"require('./package.json').version\") --after-install packaging/linux/after-install.sh --after-remove packaging/linux/after-remove.sh app_dist/linux/farmcontrol-server=/usr/bin/farmcontrol-server packaging/linux/farmcontrol-server.service=/lib/systemd/system/farmcontrol-server.service && fpm -f -s dir -t rpm -n farmcontrol-server -p app_dist/linux/ -v $(node -p \"require('./package.json').version\") --after-install packaging/linux/after-install.sh --after-remove packaging/linux/after-remove.sh app_dist/linux/farmcontrol-server=/usr/bin/farmcontrol-server packaging/linux/farmcontrol-server.service=/lib/systemd/system/farmcontrol-server.service", - "build:linux-bundle": "esbuild src/headless.js --bundle --platform=node --target=node18 --format=cjs --outfile=build/pkg-entry.cjs --packages=external --external:electron --inject:./scripts/import-meta-url.js --define:import.meta.url=import_meta_url", + "build:linux": "pnpm run cleanBuild && pnpm run build:server && pnpm run build:linux-bundle && pnpm run build:linux-pkg && pnpm run build:linux-packages", + "build:linux-bundle": "node scripts/build-linux-bundle.mjs", + "build:linux-pkg": "shx mkdir -p app_dist/linux && pnpm exec pkg build/pkg-entry.cjs --config package.json --targets node18-linux-x64 --public-packages \"pdf-to-img,pdfjs-dist,sharp,canvas,@img/*,@napi-rs/*\" --output app_dist/linux/farmcontrol-server", + "build:linux-packages": "bash scripts/build-linux-packages.sh", "cleanBuild": "rimraf build" }, "author": "Tom Butcher", @@ -70,11 +72,12 @@ ], "assets": [ "build/config.json", + "node_modules/pdf-to-img/**/*", + "node_modules/pdfjs-dist/**/*", "node_modules/sharp/**/*", "node_modules/@img/**/*", "node_modules/canvas/**/*", - "node_modules/@napi-rs/**/*", - "node_modules/pdf-to-img/**/*" + "node_modules/@napi-rs/**/*" ], "targets": [ "node18-linux-x64" diff --git a/scripts/build-linux-bundle.mjs b/scripts/build-linux-bundle.mjs new file mode 100644 index 0000000..67aeefa --- /dev/null +++ b/scripts/build-linux-bundle.mjs @@ -0,0 +1,27 @@ +import * as esbuild from 'esbuild' +import { fileURLToPath } from 'node:url' +import path from 'node:path' + +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') + +await esbuild.build({ + entryPoints: [path.join(rootDir, 'src/headless.js')], + bundle: true, + platform: 'node', + target: 'node18', + format: 'cjs', + outfile: path.join(rootDir, 'build/pkg-entry.cjs'), + external: [ + 'electron', + 'sharp', + 'canvas', + '@napi-rs/canvas', + 'pdf-to-img', + 'pdfjs-dist' + ], + inject: [path.join(rootDir, 'scripts/import-meta-url.js')], + define: { + 'import.meta.url': 'import_meta_url' + }, + logLevel: 'info' +}) diff --git a/scripts/build-linux-packages.sh b/scripts/build-linux-packages.sh new file mode 100755 index 0000000..c808840 --- /dev/null +++ b/scripts/build-linux-packages.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +version="$(node -p "require('./package.json').version")" +output_dir="app_dist/linux" +binary="${output_dir}/farmcontrol-server" + +common_fpm_args=( + -f + -s dir + -n farmcontrol-server + -p "${output_dir}/" + -v "${version}" + --after-install packaging/linux/after-install.sh + --after-remove packaging/linux/after-remove.sh + "${binary}=/usr/bin/farmcontrol-server" + packaging/linux/farmcontrol-server.service=/lib/systemd/system/farmcontrol-server.service +) + +fpm -t deb "${common_fpm_args[@]}" +fpm -t rpm "${common_fpm_args[@]}" diff --git a/src/documentprinter/interfaces/receiptinterface.js b/src/documentprinter/interfaces/receiptinterface.js index 3631c11..e471233 100644 --- a/src/documentprinter/interfaces/receiptinterface.js +++ b/src/documentprinter/interfaces/receiptinterface.js @@ -2,7 +2,7 @@ import log4js from "log4js"; import { loadConfig } from "../../config.js"; import { ThermalPrinter, PrinterTypes } from "node-thermal-printer"; -import { convertPDFToImage } from "../../utils.js"; +import { convertPDFToImage } from "../../pdfUtils.js"; const config = loadConfig(); const logger = log4js.getLogger("Receipt Printer Interface"); diff --git a/src/pdfUtils.js b/src/pdfUtils.js new file mode 100644 index 0000000..763a1e6 --- /dev/null +++ b/src/pdfUtils.js @@ -0,0 +1,61 @@ +/** + * PDF conversion utilities loaded on demand so headless startup + * does not require ESM-only pdf-to-img at module load time. + */ +export async function convertPDFToImage(pdfInput, options = {}) { + const [{ pdf }, sharp] = await Promise.all([ + import('pdf-to-img'), + import('sharp') + ]) + + try { + let scale = options.scale || 2 + + const pdfOptions = { + scale, + ...options + } + + const document = await pdf(pdfInput, pdfOptions) + const outputImages = [] + + if ( + options.page_numbers && + Array.isArray(options.page_numbers) && + options.page_numbers.length > 0 + ) { + for (const pageNum of options.page_numbers) { + let image = await document.getPage(pageNum) + + if (options.width || options.height) { + const resizeOptions = {} + if (options.width) resizeOptions.width = options.width + if (options.height) resizeOptions.height = options.height + image = await sharp.default(image).resize(resizeOptions).toBuffer() + } + + outputImages.push(image) + } + } else { + for await (const image of document) { + let processedImage = image + + if (options.width || options.height) { + const resizeOptions = {} + if (options.width) resizeOptions.width = options.width + if (options.height) resizeOptions.height = options.height + processedImage = await sharp.default(image) + .resize(resizeOptions) + .toBuffer() + } + + outputImages.push(processedImage) + } + } + + return outputImages + } catch (error) { + console.error('Error converting PDF to image:', error) + throw error + } +} diff --git a/src/utils.js b/src/utils.js index b9037a0..00aa272 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,5 @@ import readline from "node:readline"; import os from "os"; -import { pdf } from "pdf-to-img"; -import sharp from "sharp"; let isPrompting = false; // prevent multiple prompts at the same time @@ -56,71 +54,3 @@ export function getDeviceInfo() { }, }; } - -/** - * Converts a PDF buffer or file path to an array of image buffers - * @param {Buffer|string} pdfInput - PDF buffer or file path - * @param {Object} options - Conversion options (optional) - * @param {number} options.scale - Scale factor for image resolution (default: 2) - * @param {number} options.width - Target width in pixels (will calculate scale if provided) - * @param {number} options.height - Target height in pixels (optional) - * @param {number[]} options.page_numbers - Array of page numbers to convert (optional, converts all pages by default) - * @returns {Promise} Array of image buffers, one for each page - */ -export async function convertPDFToImage(pdfInput, options = {}) { - try { - // pdf-to-img uses scale instead of width/height directly - // Default scale of 2 provides good quality for thermal printers - let scale = options.scale || 2; - - const pdfOptions = { - scale, - ...options, - }; - - const document = await pdf(pdfInput, pdfOptions); - const outputImages = []; - - // If specific page numbers are requested, use getPage() for each - if ( - options.page_numbers && - Array.isArray(options.page_numbers) && - options.page_numbers.length > 0 - ) { - for (const pageNum of options.page_numbers) { - // page numbers are 1-indexed in the API - let image = await document.getPage(pageNum); - - // Resize with Sharp if width or height are provided - if (options.width || options.height) { - const resizeOptions = {}; - if (options.width) resizeOptions.width = options.width; - if (options.height) resizeOptions.height = options.height; - image = await sharp(image).resize(resizeOptions).toBuffer(); - } - - outputImages.push(image); - } - } else { - // Convert all pages: async iterable to array - for await (const image of document) { - let processedImage = image; - - // Resize with Sharp if width or height are provided - if (options.width || options.height) { - const resizeOptions = {}; - if (options.width) resizeOptions.width = options.width; - if (options.height) resizeOptions.height = options.height; - processedImage = await sharp(image).resize(resizeOptions).toBuffer(); - } - - outputImages.push(processedImage); - } - } - - return outputImages; // Array of image buffers - } catch (error) { - console.error("Error converting PDF to image:", error); - throw error; - } -}