feat(pdftops): added pdftops converter (#611)

This commit is contained in:
Orban Gergo
2026-09-10 00:48:34 +03:00
committed by GitHub
parent 2ea8e00f1e
commit b9ee1f664e
4 changed files with 130 additions and 0 deletions
+1
View File
@@ -47,6 +47,7 @@ A self-hosted online file converter. Supports over a thousand different formats.
| [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 | | [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 |
| [VTracer](https://github.com/visioncortex/vtracer) | Raster to vector | 8 | 1 | | [VTracer](https://github.com/visioncortex/vtracer) | Raster to vector | 8 | 1 |
| [Markitdown](https://github.com/microsoft/markitdown) | Documents | 6 | 1 | | [Markitdown](https://github.com/microsoft/markitdown) | Documents | 6 | 1 |
| [pdftops](https://poppler.freedesktop.org/) | Documents | 1 | 2 |
<!-- many ffmpeg fileformats are duplicates --> <!-- many ffmpeg fileformats are duplicates -->
+5
View File
@@ -18,6 +18,7 @@ import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libj
import { convert as convertLibreOffice, properties as propertiesLibreOffice } from "./libreoffice"; import { convert as convertLibreOffice, properties as propertiesLibreOffice } from "./libreoffice";
import { convert as convertMsgconvert, properties as propertiesMsgconvert } from "./msgconvert"; import { convert as convertMsgconvert, properties as propertiesMsgconvert } from "./msgconvert";
import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc"; import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc";
import { convert as convertPdftops, properties as propertiesPdftops } from "./pdftops";
import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace"; import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace";
import { convert as convertresvg, properties as propertiesresvg } from "./resvg"; import { convert as convertresvg, properties as propertiesresvg } from "./resvg";
import { convert as convertImage, properties as propertiesImage } from "./vips"; import { convert as convertImage, properties as propertiesImage } from "./vips";
@@ -137,6 +138,10 @@ const properties: Record<
properties: propertiesMarkitdown, properties: propertiesMarkitdown,
converter: convertMarkitdown, converter: convertMarkitdown,
}, },
pdftops: {
properties: propertiesPdftops,
converter: convertPdftops,
},
}; };
function chunks<T>(arr: T[], size: number): T[][] { function chunks<T>(arr: T[], size: number): T[][] {
+47
View File
@@ -0,0 +1,47 @@
import { execFile as execFileOriginal } from "node:child_process";
import { ExecFileFn } from "./types";
export const properties = {
from: {
document: ["pdf"],
},
to: {
document: ["eps", "ps"],
},
};
export async function convert(
filePath: string,
fileType: string,
convertTo: string,
targetPath: string,
options?: unknown,
execFile: ExecFileFn = execFileOriginal, // to make it mockable
): Promise<string> {
const args: string[] = [];
if (convertTo === "eps") {
args.push("-eps");
}
args.push(filePath, targetPath);
return new Promise((resolve, reject) => {
execFile("pdftops", args, (error, stdout, stderr) => {
if (error) {
reject(`error: ${error}`);
return;
}
if (stdout) {
console.log(`stdout: ${stdout}`);
}
if (stderr) {
console.error(`stderr: ${stderr}`);
}
resolve("Done");
});
});
}
+77
View File
@@ -0,0 +1,77 @@
import { beforeEach, expect, test } from "bun:test";
import { convert } from "../../src/converters/pdftops";
import { runCommonTests } from "./helpers/commonTests";
runCommonTests(convert);
let calls: string[][] = [];
function mockExecFile(
_cmd: string,
args: string[],
callback: (err: Error | null, stdout: string, stderr: string) => void,
) {
calls.push(args);
if (args.includes("fail.pdf")) {
callback(new Error("mock failure"), "", "Fake stderr: fail");
} else {
callback(null, "Fake stdout", "");
}
}
beforeEach(() => {
calls = [];
});
test("converts a normal file to ps", async () => {
const originalConsoleLog = console.log;
let loggedMessage = "";
console.log = (msg) => {
loggedMessage = msg;
};
const result = await convert("in.pdf", "pdf", "ps", "out.ps", undefined, mockExecFile);
console.log = originalConsoleLog;
expect(result).toBe("Done");
expect(calls[0]).toEqual(["in.pdf", "out.ps"]);
expect(loggedMessage).toBe("stdout: Fake stdout");
});
test("adds -eps flag for eps output", async () => {
const result = await convert("in.pdf", "pdf", "eps", "out.eps", undefined, mockExecFile);
expect(result).toBe("Done");
expect(calls[0]).toEqual(["-eps", "in.pdf", "out.eps"]);
});
test("fails on exec error", async () => {
expect(convert("fail.pdf", "pdf", "ps", "output.ps", undefined, mockExecFile)).rejects.toMatch(
/error: Error: mock failure/,
);
});
test("logs stderr when execFile returns only stderr and no error", async () => {
const originalConsoleError = console.error;
let loggedMessage = "";
console.error = (msg) => {
loggedMessage = msg;
};
const mockExecFileStderrOnly = (
_cmd: string,
_args: string[],
callback: (err: Error | null, stdout: string, stderr: string) => void,
) => {
callback(null, "", "Only stderr output");
};
await convert("input.pdf", "pdf", "ps", "output.ps", undefined, mockExecFileStderrOnly);
console.error = originalConsoleError;
expect(loggedMessage).toBe("stderr: Only stderr output");
});