feat: add Sign PDF tool (draw/type/upload signatures, place on a PDF) (#370)

Draw, type, or upload a signature and place resizable/rotatable copies across PDF pages; output flattened server-side with PyMuPDF. Visual electronic signature, not cryptographic. New interactive-sign display mode (pdf.js + Konva) and a custom docs-pool route.
This commit is contained in:
SnapOtter
2026-06-29 11:01:25 +08:00
committed by GitHub
parent 1c202c6ef0
commit 0cdd560ac4
51 changed files with 2223 additions and 10 deletions
+1
View File
@@ -45,6 +45,7 @@ export {
pdfMetadataSetPy,
pdfPageCountPy,
pdfRedactPy,
pdfSignPy,
pdfTextPy,
pdfToWordPy,
} from "./python-docs.js";
+28
View File
@@ -1,4 +1,5 @@
import { runDocsScript } from "@snapotter/ai";
import type { SignPlacement } from "@snapotter/shared";
/** Page count via the docs-profile Python dispatcher (pikepdf). */
export async function pdfPageCountPy(absPath: string): Promise<number> {
@@ -119,3 +120,30 @@ export async function htmlToPdfPy(
throw new Error(`doc_html_pdf failed: ${parsed.error}`);
}
}
/** Stamp signature images onto a PDF (PyMuPDF insert_image), flattened. */
export async function pdfSignPy(
inPath: string,
outPath: string,
signatures: string[],
placements: SignPlacement[],
): Promise<{ placed: number }> {
const stdout = await runDocsScript("doc_sign", {
input: inPath,
output: outPath,
signatures,
placements,
});
const parsed = JSON.parse(stdout.trim()) as {
ok?: boolean;
placed?: number;
error?: string;
};
if (parsed.error) {
throw new Error(`doc_sign failed: ${parsed.error}`);
}
if (typeof parsed.placed !== "number") {
throw new Error(`doc_sign failed: ${stdout.slice(0, 200)}`);
}
return { placed: parsed.placed };
}