PDF Tools
The thin slice of PDF engineering that needs a real browser, an archival converter, a signer, or qpdf. Everything else — merging, page edits, watermarks, form filling, redaction, stamping — is one or two pymupdf calls; no skill required.
When to use: Generating pixel-perfect PDFs from HTML/React, converting or validating PDF/A, tagging PDFs for accessibility, digitally signing documents, or repairing/encrypting/linearizing PDFs with qpdf.
When NOT to use:
- Content extraction (text, tables, OCR, Markdown) → use the
parse-docs skill, which routes to pdf-to-markdown, pymupdf-pdf, liteparse, or mineru.
- Routine page edits (merge, split, rotate, crop, watermark, fill forms, redact) →
pymupdf directly: insert_pdf, set_toc, insert_textbox, show_pdf_page, apply_redactions, widget field updates.
Prerequisites — install check
Check what your task needs and install only what's missing — a missing tool is not an error unless your workflow reaches for it.
Check
# Node.js >= 22 — required by Puppeteer 25.x
node --version
# JS packages — run from the project that will use them
for pkg in puppeteer @signpdf/signpdf; do
npm ls --depth=0 "$pkg" >/dev/null 2>&1 && echo "$pkg: ok" || echo "$pkg: MISSING"
done
# CLI tools
for cmd in qpdf gs verapdf; do
command -v "$cmd" >/dev/null 2>&1 && echo "$cmd: ok" || echo "$cmd: MISSING"
done
Install
# JS — install into the project you're working in
npm i puppeteer # HTML→PDF; downloads its own Chromium
npm i @signpdf/signpdf @signpdf/signer-p12 @signpdf/placeholder-plain \
@signpdf/utils # digital signatures
# CLI — macOS
brew install qpdf ghostscript verapdf
# CLI — Debian/Ubuntu (verapdf is not packaged; download from docs.verapdf.org)
sudo apt install qpdf ghostscript
Puppeteer downloads its own Chromium at install — no separate Chrome needed. If the browser is missing (e.g. PUPPETEER_SKIP_DOWNLOAD was set), run npx puppeteer browsers install chrome.
Quick Reference
| Task |
Tool |
Key Point |
| Generate PDF from HTML |
Puppeteer / Playwright |
page.pdf(); settle fonts via waitForNetworkIdle() (Puppeteer) or networkidle (Playwright) |
| Tagged PDF (accessibility) |
Puppeteer |
tagged: true maps HTML semantics to PDF structure tags |
| PDF/A conversion |
ghostscript |
gs -dPDFA=2; use PDF/A-2b for most archival needs |
| PDF/A / PDF/UA validation |
verapdf |
--flavour 2b / --flavour ua1 |
| Digital signatures |
@signpdf/* |
PKCS#7 signing with P12 certificates |
| Encrypt PDF |
qpdf |
AES-256: qpdf --encrypt user owner 256 -- |
| Repair corrupted PDF |
qpdf |
qpdf input.pdf --replace-input |
| Linearize (fast web view) |
qpdf |
qpdf --linearize input.pdf output.pdf |
Common Mistakes
| Mistake |
Correct Pattern |
| Using canvas drawing commands for PDF generation |
Use Puppeteer/Playwright with HTML/CSS templates |
Relying on window.print() for server-side generation |
Use headless browser APIs (page.pdf()) for deterministic output |
| Skipping font embedding in containers |
Embed Google Fonts or WOFF2 files with Puppeteer |
| Storing unencrypted PDFs with PII in public storage |
Apply AES-256 encryption via qpdf before storage |
References
- High-Fidelity Generation -- Puppeteer HTML-to-PDF, CSS print tips, React templates, browser pooling
- Security and Archival -- qpdf encrypt/repair/linearize, PDF/A via ghostscript + verapdf, tagged PDFs, @signpdf signing
Self-Test
After changing SKILL.md or any reference, run the offline self-test. It checks structure (dead links, orphans), doc drift (deprecated API patterns, parse-docs routing consistency, the prerequisites Check block), and live-executes what's checkable offline (ghostscript). Missing deps SKIP with an install hint — they never fail:
python3 evals/selftest.py
1---2name: pdf-tools3description: PDF output-side engineering for what pymupdf cannot do: HTML-to-PDF generation with Puppeteer (print CSS, web fonts, React templates), PDF/A archival compliance via ghostscript and verapdf, PKCS#7 digital signing with @signpdf, and qpdf encryption/repair/linearization. NOT for content extraction — use the parse-docs skill. Routine page edits (merge, split, rotate, crop, watermark, forms, redaction) need no skill — use pymupdf directly.4license: MIT5---67# PDF Tools89The thin slice of PDF engineering that needs a real browser, an archival converter, a signer, or qpdf. Everything else — merging, page edits, watermarks, form filling, redaction, stamping — is one or two pymupdf calls; no skill required.1011**When to use**: Generating pixel-perfect PDFs from HTML/React, converting or validating PDF/A, tagging PDFs for accessibility, digitally signing documents, or repairing/encrypting/linearizing PDFs with qpdf.1213**When NOT to use**:14- **Content extraction** (text, tables, OCR, Markdown) → use the `parse-docs` skill, which routes to `pdf-to-markdown`, `pymupdf-pdf`, `liteparse`, or `mineru`.15- **Routine page edits** (merge, split, rotate, crop, watermark, fill forms, redact) → `pymupdf` directly: `insert_pdf`, `set_toc`, `insert_textbox`, `show_pdf_page`, `apply_redactions`, widget field updates.1617## Prerequisites — install check1819Check what your task needs and install only what's missing — a missing tool is **not** an error unless your workflow reaches for it.2021### Check2223```bash24# Node.js >= 22 — required by Puppeteer 25.x25node --version2627# JS packages — run from the project that will use them28for pkg in puppeteer @signpdf/signpdf; do29 npm ls --depth=0 "$pkg" >/dev/null 2>&1 && echo "$pkg: ok" || echo "$pkg: MISSING"30done3132# CLI tools33for cmd in qpdf gs verapdf; do34 command -v "$cmd" >/dev/null 2>&1 && echo "$cmd: ok" || echo "$cmd: MISSING"35done36```3738### Install3940```bash41# JS — install into the project you're working in42npm i puppeteer # HTML→PDF; downloads its own Chromium43npm i @signpdf/signpdf @signpdf/signer-p12 @signpdf/placeholder-plain \44 @signpdf/utils # digital signatures4546# CLI — macOS47brew install qpdf ghostscript verapdf4849# CLI — Debian/Ubuntu (verapdf is not packaged; download from docs.verapdf.org)50sudo apt install qpdf ghostscript51```5253Puppeteer downloads its own Chromium at install — no separate Chrome needed. If the browser is missing (e.g. `PUPPETEER_SKIP_DOWNLOAD` was set), run `npx puppeteer browsers install chrome`.5455## Quick Reference5657| Task | Tool | Key Point |58| -------------------------- | ----------------------------- | -------------------------------------------------------------------------- |59| Generate PDF from HTML | Puppeteer / Playwright | `page.pdf()`; settle fonts via `waitForNetworkIdle()` (Puppeteer) or `networkidle` (Playwright) |60| Tagged PDF (accessibility) | Puppeteer | `tagged: true` maps HTML semantics to PDF structure tags |61| PDF/A conversion | ghostscript | `gs -dPDFA=2`; use PDF/A-2b for most archival needs |62| PDF/A / PDF/UA validation | verapdf | `--flavour 2b` / `--flavour ua1` |63| Digital signatures | @signpdf/\* | PKCS#7 signing with P12 certificates |64| Encrypt PDF | qpdf | AES-256: `qpdf --encrypt user owner 256 --` |65| Repair corrupted PDF | qpdf | `qpdf input.pdf --replace-input` |66| Linearize (fast web view) | qpdf | `qpdf --linearize input.pdf output.pdf` |6768## Common Mistakes6970| Mistake | Correct Pattern |71| ------------------------------------------------------ | ----------------------------------------------------------------- |72| Using canvas drawing commands for PDF generation | Use Puppeteer/Playwright with HTML/CSS templates |73| Relying on `window.print()` for server-side generation | Use headless browser APIs (`page.pdf()`) for deterministic output |74| Skipping font embedding in containers | Embed Google Fonts or WOFF2 files with Puppeteer |75| Storing unencrypted PDFs with PII in public storage | Apply AES-256 encryption via qpdf before storage |7677## References7879- [High-Fidelity Generation](references/high-fidelity-generation.md) -- Puppeteer HTML-to-PDF, CSS print tips, React templates, browser pooling80- [Security and Archival](references/security-and-archival.md) -- qpdf encrypt/repair/linearize, PDF/A via ghostscript + verapdf, tagged PDFs, @signpdf signing8182## Self-Test8384After changing SKILL.md or any reference, run the offline self-test. It checks structure (dead links, orphans), doc drift (deprecated API patterns, parse-docs routing consistency, the prerequisites Check block), and live-executes what's checkable offline (ghostscript). Missing deps SKIP with an install hint — they never fail:8586```bash87python3 evals/selftest.py88```