diff --git a/bun.lock b/bun.lock index 1aa5fd4..4de200a 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,6 @@ "": { "name": "farmcontrol-server", "dependencies": { - "@napi-rs/canvas": "^1.0.3", "axios": "^1.16.1", "canvas": "^3.2.3", "chalk": "5.4.1", @@ -21,7 +20,6 @@ "mongoose": "^9.6.2", "node-cache": "^5.1.2", "node-thermal-printer": "^4.6.0", - "pdf-to-img": "^6.1.0", "sharp": "^0.34.5", "socket.io": "^4.8.3", "socket.io-client": "^4.8.3", @@ -310,30 +308,6 @@ "@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.4.11", "", { "dependencies": { "sparse-bitfield": "3.0.3" } }, "sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA=="], - "@napi-rs/canvas": ["@napi-rs/canvas@1.0.3", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "1.0.3", "@napi-rs/canvas-darwin-arm64": "1.0.3", "@napi-rs/canvas-darwin-x64": "1.0.3", "@napi-rs/canvas-linux-arm-gnueabihf": "1.0.3", "@napi-rs/canvas-linux-arm64-gnu": "1.0.3", "@napi-rs/canvas-linux-arm64-musl": "1.0.3", "@napi-rs/canvas-linux-riscv64-gnu": "1.0.3", "@napi-rs/canvas-linux-x64-gnu": "1.0.3", "@napi-rs/canvas-linux-x64-musl": "1.0.3", "@napi-rs/canvas-win32-arm64-msvc": "1.0.3", "@napi-rs/canvas-win32-x64-msvc": "1.0.3" } }, "sha512-OlI657a5XXvKGFX7kNeIzJ8rO7IXt87Mqu2H8rXE46viAuOfum/JA7ysX7+eBhxNKznT+RCZh418mndlcFX3+w=="], - - "@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-7kSCdUhoXiO+AaIMXdBGdtp6EctZNkmF62Rea/BmVQlwKaM3bBhOzyGUzxyxz9dv5vdBfpyAaxhSRSJF4kqK4A=="], - - "@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ds14V1BPagLszQyaDTeggny5fNeTCqsUQ5QhFj9VDxSEfzrVxXtdbR0LoFyKa0Siaaw8KvqSk4t7k/WoZJwvbg=="], - - "@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@1.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-qof3LRAAycmkV2I1izZo9RoSHF8kCQr5O05sFwv0jK8rSdYV6KHVwimo6Qb7RxZj40WHKbLHm5JDaUF0o5XUAA=="], - - "@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@1.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-FU2kKZLmolHA9+KcUA+l1+xH3WTLUUTQDU/kLv9SEUr2TrRPu94aytOeizFJDHPs/QBcw4QL1mCQhetQXYBbag=="], - - "@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-GVSjntxKeA+/y/ZKf1F+cmUw1WeIkE5aMRPqnZUlBTBvBcrvgWccJAWuYCKPX4QJQwZILIIwhgdAbl51yj6fpA=="], - - "@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-J51oK/axyZ13kxycumSMfLiDZMdWdOVvqDFI28BpuViZHE3A0bQfr8B5vg8YnPEnqLD3BSn1hkdlh2buspEcNQ=="], - - "@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@1.0.3", "", { "os": "linux", "cpu": "none" }, "sha512-CtQgQjoVTX67jS9XuCTtJ40Sl7wRLMguoFnnGnfDmCWf7kzKFZVwj5ynqUOIGKFMSB61ZCuQlwPvVNxYTTseaw=="], - - "@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-jtfzAHFp+FRaR7zGT4jyCe6wUgAG/dVb5A4Apd8FY9jKarntDfUAlJXscugiH7ZF5kKnu7/lHFk9LaDPcrGEVQ=="], - - "@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-xTzaUCKUHTY4bCGadeeRZggbRVbGUT1petg7Z8r9AJR2+D9Bqu6nQAgqBGC6D47tA70LjaaaLTrJ7wNY1T74dg=="], - - "@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/canvas-win32-arm64-msvc@1.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-ktVLuBkI6QVOm5BwO/WbdGwxgeetAMJa7TTmR8qBarXF0OU2NKjvjUtPJAl2y8t+zBRczJl/1VOl9gua6WcK2g=="], - - "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@1.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-SGhlQ8bDjL1Cz2KnsKMasr/5sTcwG/SZkB6WCJxLsmSm/3aS2C+3p39bA7iZ2/94+NkVDySZfbiGoaSZSFHYxA=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "0.10.2" }, "peerDependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0" } }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="], "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], @@ -1172,8 +1146,6 @@ "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], - "node-readable-to-web-readable-stream": ["node-readable-to-web-readable-stream@0.4.2", "", {}, "sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ=="], - "node-releases": ["node-releases@2.0.47", "", {}, "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og=="], "node-thermal-printer": ["node-thermal-printer@4.6.0", "", { "dependencies": { "iconv-lite": "0.6.3", "pngjs": "7.0.0", "unorm": "1.6.0", "write-file-queue": "0.0.1" } }, "sha512-nl/beSbygyBf4qv6zDZimiD78S2VrMrpffULZWUgKxNjMUfzV2S0MO4/Qposrn2QZosVrQSGYq49VN1HIecw+A=="], @@ -1224,10 +1196,6 @@ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], - "pdf-to-img": ["pdf-to-img@6.2.0", "", { "dependencies": { "pdfjs-dist": "5.6.205" }, "bin": { "pdf2img": "bin/cli.mjs" } }, "sha512-54CaUwpTXw0SkG6U3Twx7AJNM2ZuFNpDCJ7oHmmW113gT8RwLZSbzv4/GOr+H0PFpFisuHcUirZBKgXkJXA1zg=="], - - "pdfjs-dist": ["pdfjs-dist@5.6.205", "", { "optionalDependencies": { "@napi-rs/canvas": "0.1.100", "node-readable-to-web-readable-stream": "0.4.2" } }, "sha512-tlUj+2IDa7G1SbvBNN74UHRLJybZDWYom+k6p5KIZl7huBvsA4APi6mKL+zCxd3tLjN5hOOEE9Tv7VdzO88pfg=="], - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], @@ -1682,8 +1650,6 @@ "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "pdfjs-dist/@napi-rs/canvas": ["@napi-rs/canvas@0.1.100", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "0.1.100", "@napi-rs/canvas-darwin-arm64": "0.1.100", "@napi-rs/canvas-darwin-x64": "0.1.100", "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", "@napi-rs/canvas-linux-arm64-musl": "0.1.100", "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", "@napi-rs/canvas-linux-x64-gnu": "0.1.100", "@napi-rs/canvas-linux-x64-musl": "0.1.100", "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", "@napi-rs/canvas-win32-x64-msvc": "0.1.100" } }, "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA=="], - "png-to-ico/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="], "png-to-ico/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="], @@ -1784,28 +1750,6 @@ "jest-watcher/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@0.1.100", "", { "os": "android", "cpu": "arm64" }, "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ=="], - - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@0.1.100", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A=="], - - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@0.1.100", "", { "os": "darwin", "cpu": "x64" }, "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw=="], - - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@0.1.100", "", { "os": "linux", "cpu": "arm" }, "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA=="], - - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@0.1.100", "", { "os": "linux", "cpu": "arm64" }, "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw=="], - - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@0.1.100", "", { "os": "linux", "cpu": "arm64" }, "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ=="], - - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@0.1.100", "", { "os": "linux", "cpu": "none" }, "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw=="], - - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@0.1.100", "", { "os": "linux", "cpu": "x64" }, "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg=="], - - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@0.1.100", "", { "os": "linux", "cpu": "x64" }, "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA=="], - - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/canvas-win32-arm64-msvc@0.1.100", "", { "os": "win32", "cpu": "arm64" }, "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw=="], - - "pdfjs-dist/@napi-rs/canvas/@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.100", "", { "os": "win32", "cpu": "x64" }, "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA=="], - "socket.io/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "socket.io/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], diff --git a/package.json b/package.json index ac41241..37710c6 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "author": "Tom Butcher", "license": "ISC", "dependencies": { - "@napi-rs/canvas": "^1.0.3", "axios": "^1.16.1", "canvas": "^3.2.3", "chalk": "5.4.1", @@ -39,7 +38,6 @@ "mongoose": "^9.6.2", "node-cache": "^5.1.2", "node-thermal-printer": "^4.6.0", - "pdf-to-img": "^6.1.0", "sharp": "^0.34.5", "socket.io": "^4.8.3", "socket.io-client": "^4.8.3", diff --git a/packaging/linux/farmcontrol-server.service b/packaging/linux/farmcontrol-server.service index 39f810a..e86978e 100644 --- a/packaging/linux/farmcontrol-server.service +++ b/packaging/linux/farmcontrol-server.service @@ -9,6 +9,7 @@ Restart=always User=root Group=root Environment=NODE_ENV=production +Environment=FC_PDFJS_DATA=/usr/lib/farmcontrol-server/pdfjs-data [Install] WantedBy=multi-user.target diff --git a/scripts/build-linux-binary.mjs b/scripts/build-linux-binary.mjs index 930a61c..1527469 100644 --- a/scripts/build-linux-binary.mjs +++ b/scripts/build-linux-binary.mjs @@ -59,4 +59,11 @@ if (exitCode !== 0) { process.exit(exitCode); } +const pdfjsDataDir = path.join(outputDir, "pdfjs-data"); +const pdfjsDistDir = path.join(rootDir, "node_modules/pdfjs-dist"); +await Bun.$`mkdir -p ${pdfjsDataDir}`; +await Bun.$`cp -r ${path.join(pdfjsDistDir, "standard_fonts")} ${path.join(pdfjsDataDir, "standard_fonts")}`; +await Bun.$`cp -r ${path.join(pdfjsDistDir, "cmaps")} ${path.join(pdfjsDataDir, "cmaps")}`; + console.log(`Linux binary written to ${outputFile}`); +console.log(`PDF.js data copied to ${pdfjsDataDir}`); diff --git a/scripts/build-linux-packages.sh b/scripts/build-linux-packages.sh index 588b47c..5176f37 100755 --- a/scripts/build-linux-packages.sh +++ b/scripts/build-linux-packages.sh @@ -5,6 +5,7 @@ version="$(bun --eval "console.log(JSON.parse(await Bun.file('package.json').tex arch="x64" output_dir="app_dist" binary="${output_dir}/linux/farmcontrol-server" +pdfjs_data="${output_dir}/linux/pdfjs-data" deb_name="farmcontrol-server-${version}-${arch}.deb" rpm_name="farmcontrol-server-${version}-${arch}.rpm" @@ -18,6 +19,7 @@ common_fpm_args=( --before-remove packaging/linux/before-remove.sh --after-remove packaging/linux/after-remove.sh "${binary}=/usr/bin/farmcontrol-server" + "${pdfjs_data}=/usr/lib/farmcontrol-server/pdfjs-data" packaging/linux/farmcontrol-server.service=/lib/systemd/system/farmcontrol-server.service ) diff --git a/src/pdfToImg.js b/src/pdfToImg.js new file mode 100644 index 0000000..321d697 --- /dev/null +++ b/src/pdfToImg.js @@ -0,0 +1,142 @@ +/** + * Renders PDF pages to PNG buffers using pdfjs-dist and node-canvas. + */ +import { createRequire } from "node:module"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { arrayBuffer } from "node:stream/consumers"; +import { createCanvas, DOMMatrix, ImageData } from "canvas"; +import * as pdfjs from "pdfjs-dist/legacy/build/pdf.mjs"; + +const PDF_DATA_PREFIX = "data:application/pdf;base64,"; + +if (!globalThis.DOMMatrix) { + globalThis.DOMMatrix = DOMMatrix; +} +if (!globalThis.ImageData) { + globalThis.ImageData = ImageData; +} +if (!globalThis.navigator?.language) { + globalThis.navigator = { + language: "en-US", + platform: "", + userAgent: "", + }; +} + +class CanvasPackageFactory { + constructor({ enableHWA = false } = {}) { + this.enableHWA = enableHWA; + } + + create(width, height) { + const canvas = createCanvas(width, height); + return { + canvas, + context: canvas.getContext("2d"), + }; + } + + reset(canvasAndContext, width, height) { + canvasAndContext.canvas.width = width; + canvasAndContext.canvas.height = height; + } + + destroy(canvasAndContext) { + canvasAndContext.canvas.width = 0; + canvasAndContext.canvas.height = 0; + canvasAndContext.canvas = null; + canvasAndContext.context = null; + } +} + +function getPdfjsDataDir() { + if (process.env.FC_PDFJS_DATA) { + return process.env.FC_PDFJS_DATA; + } + + const candidates = [ + "/usr/lib/farmcontrol-server/pdfjs-data", + path.join(path.dirname(process.execPath), "pdfjs-data"), + ]; + + for (const candidate of candidates) { + try { + readFileSync(path.join(candidate, "standard_fonts", "FoxitFixed.pfb")); + return candidate; + } catch {} + } + + const require = createRequire(import.meta.url); + return path.dirname(require.resolve("pdfjs-dist/package.json")); +} + +async function parsePdfInput(input) { + if (Buffer.isBuffer(input)) { + return Uint8Array.from(input); + } + if (input instanceof Uint8Array) { + return input; + } + if (typeof input === "string") { + if (input.startsWith(PDF_DATA_PREFIX)) { + return Uint8Array.from( + Buffer.from(input.slice(PDF_DATA_PREFIX.length), "base64"), + ); + } + return new Uint8Array(readFileSync(input)); + } + if (typeof input === "object" && input && Symbol.asyncIterator in input) { + return new Uint8Array(await arrayBuffer(input)); + } + + throw new Error( + "pdfToImg received an unexpected input. Provide a path, data URL, Uint8Array, Buffer, or ReadableStream.", + ); +} + +export async function pdfToImg(pdfInput, options = {}) { + const { page_numbers, scale = 2, password, docInitParams } = options; + + const pdfjsDataDir = getPdfjsDataDir(); + const data = await parsePdfInput(pdfInput); + + const pdfDocument = await pdfjs.getDocument({ + password, + standardFontDataUrl: path.join(pdfjsDataDir, "standard_fonts") + path.sep, + cMapUrl: path.join(pdfjsDataDir, "cmaps") + path.sep, + cMapPacked: true, + isEvalSupported: false, + CanvasFactory: CanvasPackageFactory, + data, + ...docInitParams, + }).promise; + + const outputImages = []; + const pageCount = pdfDocument.numPages; + const pagesToRender = + page_numbers && Array.isArray(page_numbers) && page_numbers.length > 0 + ? page_numbers + : Array.from({ length: pageCount }, (_, index) => index + 1); + + for (const pageNum of pagesToRender) { + const page = await pdfDocument.getPage(pageNum); + const viewport = page.getViewport({ scale }); + const canvasFactory = new CanvasPackageFactory(); + const { canvas, context } = canvasFactory.create( + viewport.width, + viewport.height, + ); + + await page.render({ + canvas, + viewport, + }).promise; + + outputImages.push(canvas.toBuffer("image/png")); + canvasFactory.destroy({ canvas, context }); + } + + await pdfDocument.destroy(); + return outputImages; +} diff --git a/src/pdfUtils.js b/src/pdfUtils.js index 3bf4393..087db7a 100644 --- a/src/pdfUtils.js +++ b/src/pdfUtils.js @@ -1,68 +1,35 @@ /** - * PDF conversion utilities loaded on demand so headless startup - * does not require pdf-to-img at module load time. + * PDF conversion utilities with optional post-render resizing via sharp. */ +import { pdfToImg } from "./pdfToImg.js"; + function resolveDefault(module) { return module?.default ?? module; } export async function convertPDFToImage(pdfInput, options = {}) { - const [pdfToImgModule, sharpModule] = await Promise.all([ - import("pdf-to-img"), - import("sharp"), - ]); - const { pdf } = pdfToImgModule; + const sharpModule = await import("sharp"); const sharp = resolveDefault(sharpModule); - try { - const { width, height, page_numbers, scale = 2, ...pdfOnlyOptions } = - options; + const { width, height, page_numbers, scale = 2, ...pdfOnlyOptions } = + options; - const pdfOptions = { - scale, - ...pdfOnlyOptions, - }; + const images = await pdfToImg(pdfInput, { + page_numbers, + scale, + password: pdfOnlyOptions.password, + docInitParams: pdfOnlyOptions.docInitParams, + }); - const document = await pdf(pdfInput, pdfOptions); - const outputImages = []; - - if ( - page_numbers && - Array.isArray(page_numbers) && - page_numbers.length > 0 - ) { - for (const pageNum of page_numbers) { - let image = await document.getPage(pageNum); - - if (width || height) { - const resizeOptions = {}; - if (width) resizeOptions.width = width; - if (height) resizeOptions.height = height; - image = await sharp(image).resize(resizeOptions).toBuffer(); - } - - outputImages.push(image); - } - } else { - for await (const image of document) { - let processedImage = image; - - if (width || height) { - const resizeOptions = {}; - if (width) resizeOptions.width = width; - if (height) resizeOptions.height = height; - processedImage = await sharp(image) - .resize(resizeOptions) - .toBuffer(); - } - - outputImages.push(processedImage); - } - } - - return outputImages; - } catch (error) { - console.error("Error converting PDF to image:", error); - throw error; + if (!width && !height) { + return images; } + + const resizeOptions = {}; + if (width) resizeOptions.width = width; + if (height) resizeOptions.height = height; + + return Promise.all( + images.map((image) => sharp(image).resize(resizeOptions).toBuffer()), + ); }