feat(make-pdf): diagram pre-pass — mermaid/excalidraw fences render as vector SVG; local images inline as data URIs

```mermaid / ```excalidraw fences extract to placeholder tokens, render in
one diagram-render bundle tab per run (reset contract: bundle page reloads
after any render error), and substitute back as accessible <figure> blocks
with the raw source preserved in a comment. Render failures produce a loud
red diagnostic block, never silent raw code. render=false keeps a fence as
code; title="..." becomes the aria-label and caption.

Local images now actually render: page.setContent loads at about:blank
(tab-session.ts:194), so relative paths silently 404'd before. The pre-pass
resolves them against the markdown's directory, inlines as data URIs, probes
intrinsic dimensions from the bytes (pure-TS PNG/JPEG/GIF/WebP/SVG sniffing),
and downscales rasters wider than 2x the content box at 300dpi. Remote URLs
warn (offline posture, --allow-network exempts); missing files get a visible
placeholder; --strict hard-fails both for CI pipelines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-06-11 23:59:40 -07:00
parent 69bf3b07a1
commit 67e87fe421
7 changed files with 820 additions and 3 deletions
+12
View File
@@ -324,6 +324,18 @@ function figureRules(): string {
`figure { margin: 12pt 0; }`,
`figure img { display: block; max-width: 100%; height: auto; }`,
`figcaption { font-size: 9pt; color: #666; margin-top: 6pt; font-style: italic; }`,
// Diagram figures (diagram-prepass): rendered mermaid/excalidraw SVG.
// SVGs scale to the content box and never split across pages.
`figure.diagram { break-inside: avoid; text-align: center; }`,
`figure.diagram > svg { max-width: 100%; height: auto; }`,
`figure.diagram .diagram-caption { text-align: center; }`,
// Diagnostic block for a fence that failed to render — loud, boxed,
// unmistakably an error (never silent raw code).
`figure.diagram-error { border: 1.5pt solid #b00020; padding: 8pt 10pt; text-align: left; }`,
`figure.diagram-error .diagram-error-title { font-weight: 700; color: #b00020; font-style: normal; margin: 0 0 6pt; }`,
`figure.diagram-error .diagram-error-detail { font-size: 8.5pt; white-space: pre-wrap; margin: 0; }`,
// Missing local image placeholder (non-strict mode).
`.image-missing { display: inline-block; border: 1pt dashed #b00020; color: #b00020; padding: 4pt 8pt; font-size: 9pt; }`,
].join("\n");
}