mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-05 21:25:27 +02:00
3af86348f6
Turn markdown into publication-quality PDFs. $P generate input.md out.pdf
produces a PDF with 1in margins, intelligent page breaks, page numbers,
running header, CONFIDENTIAL footer, and curly quotes/em dashes — all on
Helvetica so copy-paste extraction works ("S ai li ng" bug avoided).
Architecture (per Codex round 2):
markdown → render.ts (marked + sanitize + smartypants) → orchestrator
→ $B newtab --json → $B load-html --tab-id → $B js (poll Paged.js)
→ $B pdf --tab-id → $B closetab
browseClient.ts shells out to the compiled browse CLI rather than
duplicating Playwright. --tab-id isolation per render means parallel
$P generate calls don't race on the active tab. try/finally tab cleanup
survives Paged.js timeouts, browser crashes, and output-path failures.
Features in v1:
--cover left-aligned cover page (eyebrow + title + hairline rule)
--toc clickable static TOC (Paged.js page numbers deferred)
--watermark <text> diagonal DRAFT/CONFIDENTIAL layer
--no-chapter-breaks opt out of H1-starts-new-page
--page-numbers "N of M" footer (default on)
--tagged --outline accessible PDF + bookmark outline (default on)
--allow-network opt in to external image loading (default off for privacy)
--quiet --verbose stderr control
Design decisions locked from the /plan-design-review pass:
- Helvetica everywhere (Chromium emits single-word Tj operators for
system fonts; bundled webfonts emit per-glyph and break extraction).
- Left-aligned body, flush-left paragraphs, no text-indent, 12pt gap.
- Cover shares 1in margins with body pages; no flexbox-center, no
inset padding.
- The reference HTMLs at .context/designs/*.html are the implementation
source of truth for print-css.ts.
Tests (56 unit + 1 E2E combined-features gate):
- smartypants: code/URL-safe, verified against 10 fixtures
- sanitizer: strips <script>/<iframe>/on*/javascript: URLs
- render: HTML assembly, CJK fallback, cover/TOC/chapter wrap
- print-css: all @page rules, margin variants, watermark
- pdftotext: normalize()+copyPasteGate() cross-OS tolerance
- browseClient: binary resolution + typed error propagation
- combined-features gate (P0): 2-chapter fixture with smartypants +
hyphens + ligatures + bold/italic + inline code + lists + blockquote
passes through PDF → pdftotext → expected.txt diff
Deferred to Phase 4 (future PR): Paged.js vendored for accurate TOC page
numbers, highlight.js for syntax highlighting, drop caps, pull quotes,
two-column, CMYK, watermark visual-diff acceptance.
Plan: .context/ceo-plans/2026-04-19-perfect-pdf-generator.md
References: .context/designs/make-pdf-*.html
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
162 lines
4.9 KiB
Cheetah
162 lines
4.9 KiB
Cheetah
---
|
|
name: make-pdf
|
|
preamble-tier: 1
|
|
version: 1.0.0
|
|
description: |
|
|
Turn any markdown file into a publication-quality PDF. Proper 1in margins,
|
|
intelligent page breaks, page numbers, cover pages, running headers, curly
|
|
quotes and em dashes, clickable TOC, diagonal DRAFT watermark. Output you'd
|
|
send to a VC partner, a book agent, a judge, or Rick Rubin's team. Not a
|
|
draft artifact — a finished artifact. Use when asked to "make a PDF",
|
|
"export to PDF", "turn this markdown into a PDF", or "generate a document".
|
|
(gstack)
|
|
voice-triggers:
|
|
- "make this a pdf"
|
|
- "make it a pdf"
|
|
- "export to pdf"
|
|
- "turn this into a pdf"
|
|
- "turn this markdown into a pdf"
|
|
- "generate a pdf"
|
|
- "make a pdf from"
|
|
- "pdf this markdown"
|
|
triggers:
|
|
- markdown to pdf
|
|
- generate pdf
|
|
- make pdf
|
|
- export pdf
|
|
allowed-tools:
|
|
- Bash
|
|
- Read
|
|
- AskUserQuestion
|
|
---
|
|
|
|
{{PREAMBLE}}
|
|
|
|
# make-pdf: publication-quality PDFs from markdown
|
|
|
|
Turn `.md` files into PDFs that look like Faber & Faber essays: 1in margins,
|
|
left-aligned body, Helvetica throughout, curly quotes and em dashes, optional
|
|
cover page and clickable TOC, diagonal DRAFT watermark when you need it.
|
|
Copy-paste from the PDF produces clean words, never "S a i l i n g".
|
|
|
|
{{MAKE_PDF_SETUP}}
|
|
|
|
## Core patterns
|
|
|
|
### 80% case — memo/letter
|
|
|
|
One command, no flags. Gets a clean PDF with running header + page numbers
|
|
+ CONFIDENTIAL footer by default.
|
|
|
|
```bash
|
|
$P generate letter.md # writes /tmp/letter.pdf
|
|
$P generate letter.md letter.pdf # explicit output path
|
|
```
|
|
|
|
### Publication mode — cover + TOC + chapter breaks
|
|
|
|
```bash
|
|
$P generate --cover --toc --author "Garry Tan" --title "On Horizons" \
|
|
essay.md essay.pdf
|
|
```
|
|
|
|
Each top-level H1 in the markdown starts a new page. Disable with
|
|
`--no-chapter-breaks` for memos that happen to have multiple H1s.
|
|
|
|
### Draft-stage watermark
|
|
|
|
```bash
|
|
$P generate --watermark DRAFT memo.md draft.pdf
|
|
```
|
|
|
|
Diagonal 10% opacity DRAFT across every page. When the draft is final, drop
|
|
the flag and regenerate.
|
|
|
|
### Fast iteration via preview
|
|
|
|
```bash
|
|
$P preview essay.md
|
|
```
|
|
|
|
Renders HTML with the same print CSS and opens it in your browser. Refresh
|
|
as you edit the markdown. Skip the PDF round trip until you're ready.
|
|
|
|
### Brand-free (no CONFIDENTIAL footer)
|
|
|
|
```bash
|
|
$P generate --no-confidential memo.md memo.pdf
|
|
```
|
|
|
|
## Common flags
|
|
|
|
```
|
|
Page layout:
|
|
--margins <dim> 1in (default) | 72pt | 2.54cm | 25mm
|
|
--page-size letter|a4|legal
|
|
|
|
Structure:
|
|
--cover Cover page (title, author, date, hairline rule)
|
|
--toc Clickable TOC with page numbers
|
|
--no-chapter-breaks Don't start a new page at every H1
|
|
|
|
Branding:
|
|
--watermark <text> Diagonal watermark ("DRAFT", "CONFIDENTIAL")
|
|
--header-template <html> Custom running header
|
|
--footer-template <html> Custom footer (mutex with --page-numbers)
|
|
--no-confidential Suppress the CONFIDENTIAL right-footer
|
|
|
|
Output:
|
|
--page-numbers "N of M" footer (default on)
|
|
--tagged Accessible PDF (default on)
|
|
--outline PDF bookmarks from headings (default on)
|
|
--quiet Suppress progress on stderr
|
|
--verbose Per-stage timings
|
|
|
|
Network:
|
|
--allow-network Fetch external images. Off by default
|
|
(blocks tracking pixels).
|
|
|
|
Metadata:
|
|
--title "..." Document title (defaults to first H1)
|
|
--author "..." Author for cover + PDF metadata
|
|
--date "..." Date for cover (defaults to today)
|
|
```
|
|
|
|
## When Claude should run it
|
|
|
|
Watch for markdown-to-PDF intent. Any of these patterns → run `$P generate`:
|
|
|
|
- "Can you make this markdown a PDF"
|
|
- "Export it as a PDF"
|
|
- "Turn this letter into a PDF"
|
|
- "I need a PDF of the essay"
|
|
- "Print this as a PDF for me"
|
|
|
|
If the user has a `.md` file open and says "make it look nice", propose
|
|
`$P generate --cover --toc` and ask before running.
|
|
|
|
## Debugging
|
|
|
|
- Output looks empty / blank → check browse daemon is running: `$B status`.
|
|
- Fragmented text on copy-paste → highlight.js output (Phase 4). Retry with
|
|
`--no-syntax` once that flag exists. For now, remove fenced code blocks
|
|
and regenerate.
|
|
- Paged.js timeout → probably no headings in the markdown. Drop `--toc`.
|
|
- External image missing → add `--allow-network` (understand you're giving
|
|
the markdown file permission to fetch from its image URLs).
|
|
- Generated PDF too tall/wide → `--page-size a4` or `--margins 0.75in`.
|
|
|
|
## Output contract
|
|
|
|
```
|
|
stdout: /tmp/letter.pdf ← just the path, one line
|
|
stderr: Rendering HTML... ← progress spinner (unless --quiet)
|
|
Generating PDF...
|
|
Done in 1.5s. 43 words · 22KB · /tmp/letter.pdf
|
|
|
|
exit code: 0 success / 1 bad args / 2 render error / 3 Paged.js timeout
|
|
/ 4 browse unavailable
|
|
```
|
|
|
|
Capture the path: `PDF=$($P generate letter.md)` — then use `$PDF`.
|