refactor(index): consolidate duplicate spawn executions (#672)

* refactor(index): remove duplicate spawn execution code

* refactor(index): use `executeBinary` in `pdfToPs`

* refactor(index): consistent styling

* refactor(index): use `executeBinary` in `pdfToPpm`
This commit is contained in:
Frazer Smith
2025-11-10 14:38:50 +00:00
committed by GitHub
parent 7a4736b5ab
commit 30c49d6406
+88 -260
View File
@@ -1,7 +1,7 @@
"use strict";
const { execFile, spawn, spawnSync } = require("node:child_process");
const { normalize, resolve: pathResolve } = require("node:path");
const { basename, normalize, resolve: pathResolve } = require("node:path");
const { platform } = require("node:process");
const { promisify } = require("node:util");
const camelCase = require("camelcase");
@@ -488,6 +488,62 @@ const PDF_INFO_PATH_REG = /(.+)pdfinfo/u;
* @property {boolean} [printVersionInfo] Print copyright and version information.
*/
/**
* @author Frazer Smith
* @description Executes a Poppler binary with the provided arguments and file input.
* @ignore
* @param {string} binary - Path to the binary to execute.
* @param {string[]} args - Array of CLI arguments to pass to the binary.
* @param {Buffer|string} [file] - File input (Buffer or path).
* @param {object} [options] - Object containing execution options.
* @param {boolean} [options.binaryOutput] - Set binary encoding for stdout.
* @param {boolean} [options.preserveWhitespace] - If true, preserves leading and trailing whitespace in the output.
* @returns {Promise<string>} A promise that resolves with stdout, or rejects with an Error.
*/
function executeBinary(binary, args, file, options = {}) {
return new Promise((resolve, reject) => {
const child = spawn(binary, args);
if (options.binaryOutput) {
child.stdout.setEncoding("binary");
}
if (Buffer.isBuffer(file)) {
child.stdin.write(file);
child.stdin.end();
}
let stdOut = "";
let stdErr = "";
child.stdout.on("data", (data) => {
stdOut += data;
});
child.stderr.on("data", (data) => {
stdErr += data;
});
child.on("close", (code) => {
/* istanbul ignore else */
if (stdOut !== "") {
resolve(options.preserveWhitespace ? stdOut : stdOut.trim());
} else if (code === 0) {
resolve(ERROR_MSGS[code]);
} else if (stdErr !== "") {
reject(new Error(stdErr.trim()));
} else {
reject(
new Error(
ERROR_MSGS[code ?? -1] ||
`${basename(binary)} ${args.join(" ")} exited with code ${code}`
)
);
}
});
});
}
/**
* @author Frazer Smith
* @description Checks each option provided is valid, of the correct type, and can be used by specified
@@ -1124,48 +1180,9 @@ class Poppler {
const acceptedOptions = this.#getAcceptedOptions("pdfFonts");
const versionInfo = await this.#getVersion(this.#pdfFontsBin);
const args = parseOptions(acceptedOptions, options, versionInfo);
args.push(Buffer.isBuffer(file) ? "-" : file);
return new Promise((resolve, reject) => {
args.push(Buffer.isBuffer(file) ? "-" : file);
const child = spawn(this.#pdfFontsBin, args);
if (Buffer.isBuffer(file)) {
child.stdin.write(file);
child.stdin.end();
}
let stdOut = "";
let stdErr = "";
child.stdout.on("data", (data) => {
stdOut += data;
});
child.stderr.on("data", (data) => {
stdErr += data;
});
child.on("close", (code) => {
/* istanbul ignore else */
if (stdOut !== "") {
resolve(stdOut.trim());
} else if (code === 0) {
resolve(ERROR_MSGS[code]);
} else if (stdErr !== "") {
reject(new Error(stdErr.trim()));
} else {
reject(
new Error(
ERROR_MSGS[code ?? -1] ||
`pdffonts ${args.join(
" "
)} exited with code ${code}`
)
);
}
});
});
return executeBinary(this.#pdfFontsBin, args, file);
}
/**
@@ -1181,51 +1198,13 @@ class Poppler {
const versionInfo = await this.#getVersion(this.#pdfImagesBin);
const args = parseOptions(acceptedOptions, options, versionInfo);
return new Promise((resolve, reject) => {
args.push(Buffer.isBuffer(file) ? "-" : file);
args.push(Buffer.isBuffer(file) ? "-" : file);
if (outputPrefix) {
args.push(outputPrefix);
}
if (outputPrefix) {
args.push(outputPrefix);
}
const child = spawn(this.#pdfImagesBin, args);
if (Buffer.isBuffer(file)) {
child.stdin.write(file);
child.stdin.end();
}
let stdOut = "";
let stdErr = "";
child.stdout.on("data", (data) => {
stdOut += data;
});
child.stderr.on("data", (data) => {
stdErr += data;
});
child.on("close", (code) => {
/* istanbul ignore else */
if (stdOut !== "") {
resolve(stdOut.trim());
} else if (code === 0) {
resolve(ERROR_MSGS[code]);
} else if (stdErr !== "") {
reject(new Error(stdErr.trim()));
} else {
reject(
new Error(
ERROR_MSGS[code ?? -1] ||
`pdfimages ${args.join(
" "
)} exited with code ${code}`
)
);
}
});
});
return executeBinary(this.#pdfImagesBin, args, file);
}
/**
@@ -1245,14 +1224,14 @@ class Poppler {
/** @type {number} */
let fileSize;
return new Promise((resolve, reject) => {
if (Buffer.isBuffer(file)) {
args.push("-");
fileSize = file.length;
} else {
args.push(file);
}
if (Buffer.isBuffer(file)) {
args.push("-");
fileSize = file.length;
} else {
args.push(file);
}
return new Promise((resolve, reject) => {
const child = spawn(this.#pdfInfoBin, args);
if (Buffer.isBuffer(file)) {
@@ -1355,55 +1334,13 @@ class Poppler {
const acceptedOptions = this.#getAcceptedOptions("pdfToCairo");
const versionInfo = await this.#getVersion(this.#pdfToCairoBin);
const args = parseOptions(acceptedOptions, options, versionInfo);
args.push(Buffer.isBuffer(file) ? "-" : file, outputFile || "-");
return new Promise((resolve, reject) => {
args.push(Buffer.isBuffer(file) ? "-" : file, outputFile || "-");
const binaryOutput =
outputFile === undefined &&
args.some((arg) => ["-singlefile", "-pdf"].includes(arg));
const child = spawn(this.#pdfToCairoBin, args);
if (
outputFile === undefined &&
args.some((arg) => ["-singlefile", "-pdf"].includes(arg))
) {
child.stdout.setEncoding("binary");
}
if (Buffer.isBuffer(file)) {
child.stdin.write(file);
child.stdin.end();
}
let stdOut = "";
let stdErr = "";
child.stdout.on("data", (data) => {
stdOut += data;
});
child.stderr.on("data", (data) => {
stdErr += data;
});
child.on("close", (code) => {
/* istanbul ignore else */
if (stdOut !== "") {
resolve(stdOut.trim());
} else if (code === 0) {
resolve(ERROR_MSGS[code]);
} else if (stdErr !== "") {
reject(new Error(stdErr.trim()));
} else {
reject(
new Error(
ERROR_MSGS[code ?? -1] ||
`pdftocairo ${args.join(
" "
)} exited with code ${code}`
)
);
}
});
});
return executeBinary(this.#pdfToCairoBin, args, file, { binaryOutput });
}
/**
@@ -1422,14 +1359,13 @@ class Poppler {
const acceptedOptions = this.#getAcceptedOptions("pdfToHtml");
const versionInfo = await this.#getVersion(this.#pdfToHtmlBin);
const args = parseOptions(acceptedOptions, options, versionInfo);
args.push(Buffer.isBuffer(file) ? "-" : file);
if (outputFile) {
args.push(outputFile);
}
return new Promise((resolve, reject) => {
args.push(Buffer.isBuffer(file) ? "-" : file);
if (outputFile) {
args.push(outputFile);
}
const child = spawn(this.#pdfToHtmlBin, args);
if (Buffer.isBuffer(file)) {
@@ -1473,41 +1409,9 @@ class Poppler {
const acceptedOptions = this.#getAcceptedOptions("pdfToPpm");
const versionInfo = await this.#getVersion(this.#pdfToPpmBin);
const args = parseOptions(acceptedOptions, options, versionInfo);
args.push(Buffer.isBuffer(file) ? "-" : file, outputPath);
return new Promise((resolve, reject) => {
args.push(Buffer.isBuffer(file) ? "-" : file, outputPath);
const child = spawn(this.#pdfToPpmBin, args);
if (Buffer.isBuffer(file)) {
child.stdin.write(file);
child.stdin.end();
}
let stdErr = "";
child.stderr.on("data", (data) => {
stdErr += data;
});
child.on("close", (code) => {
/* istanbul ignore else */
if (stdErr !== "") {
reject(new Error(stdErr.trim()));
} else if (code === 0) {
resolve(ERROR_MSGS[code]);
} else {
reject(
new Error(
ERROR_MSGS[code ?? -1] ||
`pdftoppm ${args.join(
" "
)} exited with code ${code}`
)
);
}
});
});
return executeBinary(this.#pdfToPpmBin, args, file);
}
/**
@@ -1523,48 +1427,9 @@ class Poppler {
const acceptedOptions = this.#getAcceptedOptions("pdfToPs");
const versionInfo = await this.#getVersion(this.#pdfToPsBin);
const args = parseOptions(acceptedOptions, options, versionInfo);
args.push(Buffer.isBuffer(file) ? "-" : file, outputFile || "-");
return new Promise((resolve, reject) => {
args.push(Buffer.isBuffer(file) ? "-" : file, outputFile || "-");
const child = spawn(this.#pdfToPsBin, args);
if (Buffer.isBuffer(file)) {
child.stdin.write(file);
child.stdin.end();
}
let stdOut = "";
let stdErr = "";
child.stdout.on("data", (data) => {
stdOut += data;
});
child.stderr.on("data", (data) => {
stdErr += data;
});
child.on("close", (code) => {
/* istanbul ignore else */
if (stdOut !== "") {
resolve(stdOut.trim());
} else if (code === 0) {
resolve(ERROR_MSGS[code]);
} else if (stdErr !== "") {
reject(new Error(stdErr.trim()));
} else {
reject(
new Error(
ERROR_MSGS[code ?? -1] ||
`pdftops ${args.join(
" "
)} exited with code ${code}`
)
);
}
});
});
return executeBinary(this.#pdfToPsBin, args, file);
}
/**
@@ -1580,47 +1445,10 @@ class Poppler {
const acceptedOptions = this.#getAcceptedOptions("pdfToText");
const versionInfo = await this.#getVersion(this.#pdfToTextBin);
const args = parseOptions(acceptedOptions, options, versionInfo);
args.push(Buffer.isBuffer(file) ? "-" : file, outputFile || "-");
return new Promise((resolve, reject) => {
args.push(Buffer.isBuffer(file) ? "-" : file, outputFile || "-");
const child = spawn(this.#pdfToTextBin, args);
if (Buffer.isBuffer(file)) {
child.stdin.write(file);
child.stdin.end();
}
let stdOut = "";
let stdErr = "";
child.stdout.on("data", (data) => {
stdOut += data;
});
child.stderr.on("data", (data) => {
stdErr += data;
});
child.on("close", (code) => {
/* istanbul ignore else */
if (stdOut !== "") {
resolve(options.maintainLayout ? stdOut : stdOut.trim());
} else if (code === 0) {
resolve(ERROR_MSGS[code]);
} else if (stdErr !== "") {
reject(new Error(stdErr.trim()));
} else {
reject(
new Error(
ERROR_MSGS[code ?? -1] ||
`pdftotext ${args.join(
" "
)} exited with code ${code}`
)
);
}
});
return executeBinary(this.#pdfToTextBin, args, file, {
preserveWhitespace: options.maintainLayout,
});
}