Press ⌘P while the presenter is open to write the deck to a PDF, one page
per slide at the panel's current size. Font sizes here are absolute, so
scaling to a paper size would re-flow slides rather than preserve them.
SlideCanvas is extracted out of PresenterContent so the panel and the
exporter render through one layout and the PDF cannot drift from the
screen. ImageRenderer draws each page into a CGPDFContext, which keeps
text as real text.
⌘P is caught by the local event monitor, not PresenterPanel.keyDown:
command keys route through the menu bar and performKeyEquivalent, and
this app has no menu.
Two things ImageRenderer cannot capture are pre-rendered into caches
before the render pass. YouTube thumbnails load asynchronously in
.onAppear, which never fires, so the video IDs are collected and fetched
up front. SVG backgrounds are a live WKWebView; NSImage loads those files
without error but renders them solid black, so they are snapshotted from
an offscreen web view instead — which has to belong to a window, and has
to be awaited with Task.sleep rather than a RunLoop pump, since export
runs inside a main-actor task.
Links become annotations in a PDFKit pass over the finished file. Prose
links are located by searching the page's own text for the label, since
SwiftUI exposes no per-run geometry for a Text — so real text is
load-bearing, not just nice. A wrapped label gets one annotation per
line, and repeated labels pair with their URLs in order of appearance.
YouTube thumbnails carry no text, so their rects come from a
GeometryReader probe during the render.
Bumps VERSION to 0.5.0 and documents the feature in the README, the
sample deck, cards/pdf-export.md, and the project plan.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XY9W1Au74MvRV3yBZ3yEGK