Skip to content

Render inline HTML in markdown + fix garbled PDF export#74

Merged
Razee4315 merged 2 commits into
mainfrom
feat/html-rendering-and-pdf-print
Jun 14, 2026
Merged

Render inline HTML in markdown + fix garbled PDF export#74
Razee4315 merged 2 commits into
mainfrom
feat/html-rendering-and-pdf-print

Conversation

@Razee4315

Copy link
Copy Markdown
Owner

What & why

Two fixes plus one latent-bug fix discovered along the way.

1. PDF export was garbling text (emoji, smart quotes, em dashes)

The old path drew text with jsPDF's standard fonts, which only support single-byte WinAnsi (Latin-1). Anything outside that range was emitted as raw bytes and rendered as garbage (the &-interleaved text and mangled Ø=ßâ& headings users saw). It also hand-rebuilt layout from parsed HTML, so the PDF never matched the preview.

Now: we render the same standalone HTML we already produce for HTML export inside an isolated off-screen iframe and drive the webview's own print pipeline ("Save as PDF" / "Microsoft Print to PDF"). The result matches the preview exactly — real Unicode + color emoji, selectable/searchable text, working links — at zero added bundle weight. PDF always renders the light theme so it stays legible on white paper.

  • Removes the jspdf dependency and all dead jsPDF layout code (parseHTMLForPDF, pdfFontSizes, hexToRgb, PDFElement) — net −500+ lines.
  • Hardens the print path: waits for fonts + inlined images, cleans up the iframe on afterprint with a fallback timer.
  • Better @media print CSS: @page margins, print-color-adjust for code/table/blockquote fills, sensible page-break rules.

Trade-off: PDF export now opens the OS print dialog (pick "Save as PDF") instead of a direct save dialog. This is the standard high-fidelity approach (Obsidian/Typora-style) and the only way to get correct emoji/Unicode + a layout that matches the preview.

2. Inline HTML in markdown (GitHub-style)

Adds rehype-raw + rehype-sanitize so raw HTML embedded in markdown renders like it does on GitHub, while stripping XSS vectors. Plugin order is raw → sanitize → katex → highlight → source-line so sanitize runs right after the only unsafe step and never strips KaTeX/highlight output. Sanitize schema = GitHub's defaultSchema plus two narrow allowances (math marker classes; the internal wikilink: protocol).

3. Latent bug: wikilinks never fired

react-markdown's default urlTransform stripped wikilink: hrefs to "", so the wikilink click handler could never run. A custom urlTransform now passes that scheme through (everything else still defaults, so javascript: etc. stay blocked).

Verification

  • Full markdown pipeline tested end-to-end through react-markdown: raw HTML (details/summary/kbd/sub/sup/div) renders, wikilinks/task lists/math/syntax highlighting/mermaid all preserved, and script/onerror/javascript:/iframe/onclick all stripped.
  • tsc --noEmit clean; all 122 unit tests pass.

🤖 Generated with Claude Code

Adds rehype-raw + rehype-sanitize so raw HTML embedded in markdown renders
like it does on GitHub, while stripping XSS vectors (script, event handlers,
javascript: URLs, iframe, etc.). Plugin order is raw -> sanitize -> trusted
generators (katex, highlight) -> source-line stamper so sanitize never strips
KaTeX/highlight output.

Sanitize schema = GitHub defaultSchema plus two narrow allowances: math
marker classes (so remark-math survives to rehype-katex) and the internal
wikilink: href protocol.

Also fixes a latent bug: react-markdown's default urlTransform stripped
wikilink: hrefs to empty, so wikilink clicks never fired. A custom
urlTransform now passes that scheme through (everything else still defaults,
so javascript: etc. stay blocked).
The old PDF path drew text with jsPDF's standard fonts, which only support
single-byte WinAnsi (Latin-1). Anything outside that range — emoji, curly
quotes, em dashes, arrows — was emitted as raw bytes and rendered as garbage
(e.g. '&'-interleaved text and mangled headings). It also hand-rebuilt layout
from parsed HTML, so the PDF never matched the preview.

Replace it with the webview's own print pipeline: render the same standalone
HTML used for HTML export inside an isolated off-screen iframe and call
print() ("Save as PDF"). The result matches the preview exactly with real
Unicode + color emoji, selectable text and working links, at zero added bundle
weight. PDF always renders the light theme so it stays legible on white paper.

- Remove jsPDF dependency and all dead jsPDF layout code (parseHTMLForPDF,
  pdfFontSizes, hexToRgb, PDFElement).
- Harden the print path: wait for fonts + inlined images, clean up the iframe
  on afterprint with a fallback timer.
- Enhance @media print CSS: @page margins, print-color-adjust for code/table/
  blockquote fills, sensible page-break rules.
- Refresh now-stale jsPDF comments in App.tsx / ExportMenu.tsx.
@Razee4315 Razee4315 merged commit 7857da1 into main Jun 14, 2026
2 checks passed
@Razee4315 Razee4315 deleted the feat/html-rendering-and-pdf-print branch June 14, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant