feat(tools): 2.0 phase 5 wave 2 - pdf depth (21 tools) (#220)

This commit is contained in:
SnapOtter
2026-06-13 10:18:55 +08:00
parent ae1337901d
commit 2f39e38162
128 changed files with 11381 additions and 778 deletions
+56 -24
View File
@@ -3,38 +3,20 @@ import { resolveGs } from "./binaries.js";
export type PdfCompressionPreset = "screen" | "ebook" | "printer";
/** Ghostscript re-distillation with a quality preset; 120s hard kill. */
export async function gsCompressPdf(
inputPath: string,
outPath: string,
preset: PdfCompressionPreset,
): Promise<void> {
/** @internal Shared gs CLI runner; not part of the public package API. */
function runGs(args: string[], timeoutMs = 120_000): Promise<void> {
const bin = resolveGs();
if (!bin) throw new Error("gs binary not found (set GS_PATH or install ghostscript)");
await new Promise<void>((resolvePromise, reject) => {
const child = spawn(
bin,
[
"-dSAFER",
"-dBATCH",
"-dNOPAUSE",
"-dQUIET",
"-sDEVICE=pdfwrite",
`-dPDFSETTINGS=/${preset}`,
"-dCompatibilityLevel=1.6",
`-sOutputFile=${outPath}`,
inputPath,
],
{ stdio: ["ignore", "ignore", "pipe"] },
);
return new Promise<void>((resolvePromise, reject) => {
const child = spawn(bin, args, { stdio: ["ignore", "ignore", "pipe"] });
let err = "";
let settled = false;
const timer = setTimeout(() => {
if (settled) return;
settled = true;
child.kill("SIGKILL");
reject(new Error("ghostscript timed out after 120s"));
}, 120_000);
reject(new Error(`ghostscript timed out after ${Math.round(timeoutMs / 1000)}s`));
}, timeoutMs);
child.stderr.on("data", (c: Buffer) => {
err = (err + c.toString("utf8")).slice(-4096);
});
@@ -53,3 +35,53 @@ export async function gsCompressPdf(
});
});
}
/** Ghostscript re-distillation with a quality preset. */
export async function gsCompressPdf(
inputPath: string,
outPath: string,
preset: PdfCompressionPreset,
): Promise<void> {
await runGs([
"-dSAFER",
"-dBATCH",
"-dNOPAUSE",
"-dQUIET",
"-sDEVICE=pdfwrite",
`-dPDFSETTINGS=/${preset}`,
"-dCompatibilityLevel=1.6",
`-sOutputFile=${outPath}`,
inputPath,
]);
}
/** Grayscale re-distillation via DeviceGray color conversion. */
export async function gsGrayscalePdf(inputPath: string, outPath: string): Promise<void> {
await runGs([
"-dSAFER",
"-dBATCH",
"-dNOPAUSE",
"-dQUIET",
"-sDEVICE=pdfwrite",
"-sColorConversionStrategy=Gray",
"-dProcessColorModel=/DeviceGray",
`-sOutputFile=${outPath}`,
inputPath,
]);
}
/** PDF/A-2b candidate via ghostscript's PDFA switch (no veraPDF validation this wave). */
export async function gsPdfaConvert(inputPath: string, outPath: string): Promise<void> {
await runGs([
"-dSAFER",
"-dBATCH",
"-dNOPAUSE",
"-dQUIET",
"-dPDFA=2",
"-dPDFACompatibilityPolicy=1",
"-sColorConversionStrategy=RGB",
"-sDEVICE=pdfwrite",
`-sOutputFile=${outPath}`,
inputPath,
]);
}