--- name: kagen description: Convert Kami HTML templates to production-grade PDF via Chromium/Playwright. Complements Kami (design) with PDF rendering. Use when user asks to generate PDF files, render HTML to PDF, or export documents. triggers: - "render PDF" - "convert HTML to PDF" - "PDF from template" - "Kami render" - "Kagen" - "HTML to PDF" - "Playwright PDF" - "PDF generation" - "Chromium PDF" negatives: - "design PDF" - "create template" - "HTML design" - "CSS styling" - "branding" - "document design" - "PDF design template" -> use kami license: MIT compatibility: opencode metadata: version: "1.0.0" workflow: documents audience: developers --- # kagen · 紙源 **紙源 · かげん** - paper source. PDF generation companion to Kami. Kami designs, **Kagen ships**. Converts Kami HTML templates to production-grade PDF using Chromium (Playwright), bypassing WeasyPrint's Windows limitations. ## Why Kagen | Problem | Solution | |---------|----------| | WeasyPrint doesn't work on Windows (no GTK) | Kagen uses Playwright/Chromium — works everywhere | | WeasyPrint cold-start ~630ms per render | Playwright warm ~13ms per render | | WeasyPrint can't execute JS | Chromium renders fully (charts, dynamic content) | | wkhtmltopdf is deprecated and unmaintained | Playwright is actively maintained by Microsoft | Based on benchmarks (pdf4.dev 2026), engineering discussions (HN, Stack Overflow, BrowserStack), and production experience from DocRaptor, customjs.space, and print-css.rocks. ## Prerequisites - Node.js 20+ - Playwright (`npx playwright` auto-installs on first use) - Chromium browser installed (`npx playwright install chromium`) - Kami HTML templates (any valid HTML with print CSS) ## Quick start ```powershell npx playwright pdf "file:///path/to/doc.html" "output.pdf" ``` Or from Node.js: ```js const { chromium } = require('playwright'); const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto('file:///path/to/doc.html', { waitUntil: 'networkidle' }); await page.pdf({ path: 'output.pdf', format: 'A4', printBackground: true, margin: { top: '0', bottom: '0', left: '0', right: '0' } }); await browser.close(); ``` ## Production settings (from professional research) ### Page options ```js await page.pdf({ path: 'output.pdf', format: 'A4', // or 'Letter', 'A3' printBackground: true, // always true — renders parchment bg margin: { top: '0', bottom: '0', left: '0', right: '0' }, // For screen media (not print): // await page.emulateMedia({ media: 'screen' }); }); ``` ### Critical CSS rules for reliable PDF output ```css /* Always include in your HTML template header */ @page { size: A4; margin: 24mm 26mm 26mm 26mm; background: #f5f4ed; widows: 4; orphans: 4; } /* Force background colors in Chromium */ * { -webkit-print-color-adjust: exact; print-color-adjust: exact; } /* Avoid orphan text lines — high widows/orphans prevents single-line splits */ body { widows: 4; orphans: 4; } p { widows: 3; orphans: 3; } li { widows: 2; orphans: 2; } /* Page break control — only on chapters with heavy content */ .chapter { } .chapter.break { break-before: page; } /* Never let a heading sit alone at page bottom */ h1, h2, h3, h4 { break-after: avoid; } /* Keep these blocks intact — never split across pages */ table, pre, figure, .callout, .card, blockquote, .finding-header, .takeaway { break-inside: avoid; } /* Avoid code blocks getting orphaned from their preceding paragraph */ pre { margin-top: 6pt; page-break-before: avoid; } /* Tables should not break rows across pages */ table tr { break-inside: avoid; } ``` ### Visual patterns by document type Each document type needs its own set of visual patterns. Apply accordingly: | Type | Required patterns | Optional patterns | |------|----------------------|---------------------| | **Pentest / Security report** | Severity badges (red/orange/green), risk bar, code blocks with left border, impact/remediation boxes | Finding-header with metadata, findings table with badges | | **One-pager / Executive summary** | Glance grid (4 metrics), lead paragraph, takeaway box, cover with large title + decorative line | Callout for key data point, footer with contact | | **White paper / Long doc** | Chapter breaks in dense sections, table of contents, callouts, keep-together on critical blocks | Quotes, diagrams, appendix | | **Letter** | Wide margins (25mm), formal greeting and closing, no columns, no tables | Letterhead, signature | | **Resume** | Dense body (9.2pt), metric row, project bullets with action + result | Timeline, skills grid | | **Slides** | Assertion-evidence titles, one idea per slide, one-line bullets, pinned callout | Code cards, 2x2 table | **Rule:** if the document type isn't in the table, choose the closest one and adapt. ### Near-empty pages prevention Nothing looks more amateur than a page with 2 lines. Causes and solutions: | Cause | Solution | Auto-detection | |-------|----------|---------------------| | Heading with 1 short paragraph at the end | Merge with previous section | If a chapter has only 1 h2 + 1 p, it doesn't deserve its own page | | `break-before: page` on every section | Only use on chapters with >1/3 page of content | Count paragraphs + tables + lists. If they add up to less than 5 elements, don't force a break | | `break-inside: avoid` on large block that doesn't fit | Relax `break-inside` or split the block | If a keep-together measures more than 1 page, don't force it | | Source list at the end spilling onto a separate page | Move to consolidated sources chapter | Sources go in one place, not repeated in each chapter | ### AI anti-patterns in documents Validate that content inherited from Kami doesn't have these marks: | Anti-pattern | Problem | Fix | |-------------|----------|-----| | Repeated em dash `—` as label/value separator | `Black Box — no credentials`, multiple lines in a row | Parentheses, comma, colon. The dash is for genuine asides, not for separating labels | | Uniform tables without color | All rows identical, no visual indication of severity or priority | Color badges, subtle zebra rows, highlighted first column | | Code blocks without contrast | They blend with the body, don't look like code | Left blue border, ivory background, monospace, generous padding | | Same structure on every page | Every page is title + table or title + list | Vary: finding box, risk bar, flowchart, callout. Alternate rhythm | | Claims without source | "LLMs hallucinate 15-20%" without attribution | "According to Vectara HHEM 2026, LLMs..." | ### Best practices from professionals | Practice | Source | Why | |----------|--------|-----| | Always set `printBackground: true` | BrowserStack, Playwright docs | Kami uses parchment bg #f5f4ed — Chromium strips it by default | | Use `file://` protocol | Stack Overflow, DocuPotion | Avoids auth/CORS issues with local files | | Set `waitUntil: 'networkidle'` | BrowserStack | Ensures fonts, CSS fully loaded | | Lock browser version in CI | BrowserStack, blog.rasc.ch | Chromium updates can change rendering | | Use `@page` margins, not Playwright margins | print-css.rocks, CSS Paged Media spec | CSS margins are more predictable for paged media | | `-webkit-print-color-adjust: exact` | MDN, Chromium docs | Critical for parchment backgrounds and brand colors | | Reuse browser instance (warm) | pdf4.dev benchmark | 42ms → 3ms speedup (14x) | | Validate PDF visually in CI | BrowserStack | Page count, whitespace, font check | ## Warm mode (production pipeline) ```js const { chromium } = require('playwright'); class PDFRenderer { constructor() { this.browser = null; } async start() { this.browser = await chromium.launch(); } async render(htmlPath, outputPath) { const page = await this.browser.newPage(); await page.goto('file:///' + htmlPath.replace(/\\/g, '/'), { waitUntil: 'networkidle' }); await page.pdf({ path: outputPath, format: 'A4', printBackground: true, margin: { top: '0', bottom: '0', left: '0', right: '0' } }); await page.close(); } async stop() { await this.browser.close(); } } ``` ## Font embedding Chromium embeds system fonts by default. For custom fonts (like TsangerJinKai02 in Kami): ```css @font-face { font-family: "CustomFont"; src: url("fonts/CustomFont.woff2") format("woff2"); font-weight: 400; font-style: normal; } ``` Place font files relative to the HTML and use relative `src` paths. Chromium resolves them from the HTML file's directory. ## Troubleshooting | Problem | Fix | |---------|-----| | White background instead of parchment | Add `printColorAdjust: exact` in CSS and `printBackground: true` in JS | | Fonts not rendering | Use relative paths in `@font-face`, check font file exists | | Page breaks wrong | Check `break-inside: avoid` on tables, pre, callout | | Near-empty page (2 lines alone) | Merge short section with previous; avoid `break-before: page` on light chapters | | Content overflow | Use `page-break-inside: avoid` on large blocks | | Chinese chars as boxes | Include CJK font in `@font-face` or use system CJK fallback | | PDF too large | Remove unnecessary images, compress embedded fonts | | Slow first render | Keep browser instance alive (warm mode) | ### CSS visual pattern snippets Concrete patterns to include in the HTML by document type: ```css /* Severity badges (pentest, security) */ .badge-high { background: #f5e8e8; color: #a83030; display: inline-block; padding: 2pt 7pt; border-radius: 3pt; font-size: 8pt; font-weight: 500; text-transform: uppercase; } .badge-medium { background: #f5ede2; color: #b86a25; display: inline-block; padding: 2pt 7pt; border-radius: 3pt; font-size: 8pt; font-weight: 500; text-transform: uppercase; } .badge-ok { background: #e8f2ec; color: #2a7a4a; display: inline-block; padding: 2pt 7pt; border-radius: 3pt; font-size: 8pt; font-weight: 500; text-transform: uppercase; } /* Risk bar (pentest exec summary) */ .risk-bar { display: flex; gap: 2pt; margin: 8pt 0 14pt 0; height: 12pt; } .risk-bar .seg { border-radius: 2pt; display: flex; align-items: center; justify-content: center; font-size: 7pt; color: #fff; font-weight: 500; } /* Code blocks with left border (pentest, technical) */ pre { border-left: 2.5pt solid #1B365D; border-radius: 3pt; background: #faf9f5; padding: 10pt 14pt; font-family: Consolas, monospace; font-size: 9pt; line-height: 1.5; break-inside: avoid; } /* Impact/Remediation boxes (pentest) */ .impact-box, .remediation-box { border-left: 2pt solid #e8e6dc; padding-left: 12pt; margin: 10pt 0 14pt 0; break-inside: avoid; } .impact-box .label, .remediation-box .label { font-size: 8.5pt; letter-spacing: 0.8pt; text-transform: uppercase; font-weight: 500; color: #1B365D; margin-bottom: 4pt; } /* Cover accent line */ .cover-line { width: 60pt; height: 2pt; background: #1B365D; margin: 20pt 0; border-radius: 1pt; } /* Pipeline flow (4 step horizontal) */ .pipeline-flow { display: flex; gap: 0; margin: 18pt 0; break-inside: avoid; } .pipeline-step { flex: 1; text-align: center; padding: 10pt 8pt; } .pipeline-step .dot { width: 6pt; height: 6pt; border-radius: 50%; background: #1B365D; margin: 0 auto 5pt auto; } .pipeline-step + .pipeline-step { border-left: 0.5pt dotted #e8e6dc; } .pipeline-step .name { font-size: 9pt; font-weight: 500; color: #141413; margin-bottom: 3pt; } .pipeline-step .desc { font-size: 7.5pt; color: #6b6a64; line-height: 1.35; } /* Keep-together wrapper */ .keep-together { break-inside: avoid; } ``` ### Self-review protocol (mandatory) Don't generate the PDF without passing this checklist. Each item is a real failure documented in previous iterations. ### Structural (blocking) - [ ] Each chapter has enough content to fill >1/3 of a page - [ ] No sections with just heading + 1 short paragraph (merge) - [ ] No near-empty pages (2 lines alone) - [ ] `break-before: page` only on chapters with dense content ### Visual (high priority) - [ ] Visual elements match the document type (badges, risk bars, code blocks, etc.) - [ ] Tables have proper styling (optional zebra, padding, clear headers) - [ ] Code blocks are distinguishable from body (border, background, monospace) - [ ] No repeated em dashes `—` used as label/value separators - [ ] There's variety in visual rhythm (not all pages the same) ### Content (high priority) - [ ] Every categorical claim has its visible source ("according to X...") - [ ] Internal methodology is distinguished from verified fact (disclaimer if applicable) - [ ] No redundant content between chapters (sources, repeated lists) - [ ] Every paragraph is necessary (if it can be removed without loss, remove it) ### Technical (blocking) - [ ] `printBackground: true` in Playwright configuration - [ ] `-webkit-print-color-adjust: exact` in CSS - [ ] `widows: 4; orphans: 4` in @page and body - [ ] `break-inside: avoid` on pre, table, blockquote, callout - [ ] `table tr { break-inside: avoid }` so tables don't split rows ## Complete HTML example Minimum functional template that includes all essential patterns. Copy and adapt: ```html
Document content. Verify sources (research), humanize text (humanize), design with Kami.