-
-
Notifications
You must be signed in to change notification settings - Fork 470
feat(core): export slides as PNG from the viewer and the CLI #379
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
desek
wants to merge
34
commits into
1weiho:main
Choose a base branch
from
desek:feat/export-slides-as-png
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
51b071a
checkpoint(CR-0001): CR authored for exporting slides as PNG from the…
desek 0194cd5
checkpoint(CR-0001): drop html-to-image; switch to hand-rolled foreig…
desek e9b6deb
checkpoint(CR-0001): CR reviewed: drift reconciled, contradictions re…
desek 0937115
checkpoint(CR-0001): phase 1 — locale keys + export-png skeleton
desek 2d86266
checkpoint(CR-0001): phase 2 — single-page PNG rasterisation
desek 73f8a4f
checkpoint(CR-0001): phase 3 — full-deck PNG ZIP export
desek 642e31d
checkpoint(CR-0001): phase 4 — PNG progress toast and dropdown entries
desek 14496d0
checkpoint(CR-0001): phase 5 — tests, changeset, polish
desek e3a9a95
checkpoint(CR-0001): CR finalized
desek ba413f8
checkpoint(CR-0001): validation report completed
desek 87a65dd
checkpoint(CR-0001): gaps fixed per validation report
desek 9e99e1a
checkpoint(CR-0001): documentation updated for implemented feature
desek e68c064
fix(core): make PNG export render faithfully at 1920x1080
desek 0920a2b
test(core): use explicit .ts import extensions in PNG/download tests
desek 4c23214
checkpoint(CR-0002): CR authored for programmatic CLI PNG export via …
desek f358411
checkpoint(CR-0002): resolve four open questions in draft CR
desek 3ebe121
checkpoint(CR-0002): CR reviewed: drift reconciled, contradictions re…
desek 3e2c991
checkpoint(CR-0002): resolve enumeration mechanism via new GET /__sli…
desek ad40747
checkpoint(CR-0002): phase 1 — CLI subcommand skeleton + Playwright d…
desek 4a5b131
checkpoint(CR-0002): phase 2 — viewer-side readiness signal
desek e7e6342
checkpoint(CR-0002): phase 3: Headless render loop
desek c52135b
checkpoint(CR-0002): phase 4 — polish, tests, changeset
desek 6294553
fix(core): mark playwright-chromium external in tsdown build
desek 02001d5
checkpoint(CR-0002): CR finalized
desek 121d0f9
checkpoint(CR-0002): gaps fixed per validation report
desek 7326fdf
checkpoint(CR-0002): documentation updated for implemented feature
desek 3228ed3
fix(core): render bare full-bleed slide for headless PNG export
desek c15eacf
fix(core): invoke @playwright/test cli directly in test:e2e
desek e062070
fix(core): freeze intro animations before rasterising PNG exports
desek 5e07293
docs(core): document PNG export as the agent visual-verification loop
desek 0a94961
docs(cr): reconcile CR-0001 and CR-0002 with the shipped implementation
desek 97ada08
docs(core): make code docstrings self-sufficient
desek d10841c
refactor(core): reuse shared helpers in the image PPTX exporter
desek c845070
fix(core): inline pseudo-element background images in PNG export
desek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@open-slide/core": minor | ||
| --- | ||
|
|
||
| Add an `open-slide export` command that renders deck pages to 1920x1080 PNGs via headless Chromium. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@open-slide/core": minor | ||
| --- | ||
|
|
||
| Add PNG export to the viewer download menu for the current slide or the whole deck as a ZIP. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@open-slide/core": patch | ||
| --- | ||
|
|
||
| Document PNG export as a way to check slide layout visually. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| --- | ||
| title: open-slide export | ||
| description: Render every page of every deck to PNG via headless Chromium. | ||
| --- | ||
|
|
||
| ```npm | ||
| open-slide export --all | ||
| # or | ||
| open-slide export --slide intro --page 2 | ||
| ``` | ||
|
|
||
| Boots an in-process Vite dev server on `127.0.0.1`, launches headless | ||
| Chromium via [Playwright](https://playwright.dev/), navigates the real | ||
| viewer route per page, waits for the same readiness signals the in-viewer | ||
| exporter uses (fonts, `data-waitfor`, animation settle), and writes one | ||
| 1920×1080 PNG per page to `--out`. | ||
|
|
||
| This is the pixel-perfect, scriptable counterpart to the in-viewer PNG | ||
| export ([Export](/docs/core-feature/export)): the browser's own compositor | ||
| paints, so advanced CSS (filters, blend modes, `backdrop-filter`) renders | ||
| faithfully, and there is no human in the loop. | ||
|
|
||
| ## Why: giving agents eyes | ||
|
|
||
| The primary motivation is **visual verification by an agent**. A model | ||
| writing slides is otherwise working blind — it can reason about a layout | ||
| but never see it, so it predicts the vertical budget with arithmetic and | ||
| hopes. A 1920×1080 PNG closes that loop: frontier models with | ||
| high-resolution image understanding can read these files directly and | ||
| check the deck the way a human reviewer would. | ||
|
|
||
| That catches the defect classes static analysis cannot: | ||
|
|
||
| - **Clipping** — content pushed past the 1080px bottom edge, silently cropped. | ||
| - **Overflow** — text escaping its card, column, or safe area. | ||
| - **Aspect-ratio distortion** — images stretched or squashed by a bad `object-fit`. | ||
| - **Collision and overlap** — a heading landing on a figure, a caption on a chart. | ||
| - **Legibility** — type too small, or contrast too low, to read at projector distance. | ||
|
|
||
| The workflow is: author the deck, export the pages, look at them, fix what | ||
| the images reveal, re-export. The bundled `slide-authoring` skill builds | ||
| this into its self-review checklist, so agents following it verify their | ||
| own output instead of declaring success unseen. | ||
|
|
||
| Because each file is exactly 1920×1080 — the canvas as the audience sees | ||
| it — what the model inspects is what gets presented, not an approximation. | ||
|
|
||
| The same properties make it suitable for CI thumbnail regeneration, visual | ||
| regression diffing, and batch pipelines. | ||
|
|
||
| ## Prerequisite: install Playwright | ||
|
|
||
| `playwright-chromium` is **not** a runtime dependency of `@open-slide/core` | ||
| — end-user installs stay lean. Install it on demand in the workspace that | ||
| runs the export: | ||
|
|
||
| ```bash | ||
| pnpm add -D playwright-chromium | ||
| npx playwright install chromium | ||
| ``` | ||
|
|
||
| If Playwright is missing, `open-slide export` preflight-fails before | ||
| booting anything, exits with code `2`, and prints both commands above as a | ||
| single-paragraph, copy-pasteable hint. On Linux CI images you may also need | ||
| `npx playwright install-deps chromium` for the Chromium shared libraries. | ||
|
|
||
| ## Flags | ||
|
|
||
| | Flag | Default | Description | | ||
| | --------------------- | -------------- | --------------------------------------------------------------------------- | | ||
| | `--slide <id>` | — | Restrict to a single deck (the `slideId` that appears in `/s/:slideId`). | | ||
| | `--all` | off | Export every discoverable deck. Mutually exclusive with `--slide`. | | ||
| | `--page <n>` | — | Export a single 1-based page index. Requires `--slide`. | | ||
| | `--out <dir>` | `./png-export` | Destination directory (created if missing). | | ||
| | `--port <port>` | OS-assigned | Pin the in-process dev server's port instead of an ephemeral one. | | ||
| | `--timeout <ms>` | `15000` | Per-page readiness timeout. On timeout the page is captured anyway. | | ||
|
|
||
| Either `--slide` or `--all` is required. `--page` without `--slide` is a | ||
| usage error. Misuse exits with code `2`. | ||
|
|
||
| ## Output | ||
|
|
||
| Files are written atomically (write to `<file>.tmp`, then rename) into | ||
| `--out` as: | ||
|
|
||
| ```text | ||
| {slideId}-p{N}.png | ||
| ``` | ||
|
|
||
| `N` is the 1-based page index, zero-padded to the total page count's width | ||
| — so a 9-page deck yields `slide-p1.png`…`slide-p9.png` (width 1) and a | ||
| 100-page deck yields `slide-p001.png`…`slide-p100.png` (width 3). This | ||
| matches the in-viewer PNG export's filename convention. | ||
|
|
||
| Each file is exactly 1920×1080, enforced by both Playwright's viewport | ||
| size and a `clip` on `page.screenshot()`. | ||
|
|
||
| ## Console output | ||
|
|
||
| One greppable line per page, plus a summary on success: | ||
|
|
||
| ```text | ||
| intro:p1 → png-export/intro-p1.png | ||
| intro:p2 → png-export/intro-p2.png | ||
| outro:p1 → png-export/outro-p1.png | ||
| Exported 3 page(s) from 2 deck(s) to png-export | ||
| ``` | ||
|
|
||
| If a page's readiness signal does not resolve within `--timeout`, the CLI | ||
| logs a single warning naming the slide and page, captures the current | ||
| frame anyway, and continues. | ||
|
|
||
| ## Exit codes | ||
|
|
||
| | Code | Meaning | | ||
| | ---- | ------------------------------------------------------------------------- | | ||
| | `0` | Success. | | ||
| | `1` | Unrecoverable runtime error (dev server failed, Chromium crashed, etc.). | | ||
| | `2` | Usage / preflight error (missing Playwright, bad flags, unknown `--slide`). | | ||
|
|
||
| The presenter route (`/s/:slideId/presenter`) is not captured — only the | ||
| viewer surface. JPEG, WebP, PDF, custom resolutions, and parallel | ||
| rendering are not supported. | ||
|
|
||
| ## In-viewer vs. headless | ||
|
|
||
| | Path | Where | Renderer | Use when | | ||
| | ------------------------------------- | ------------------- | --------------------------------------- | ------------------------------------------------------- | | ||
| | Toolbar **Export → … as PNG** | Browser (live deck) | Client-side `<foreignObject>` rasterise | Interactive: an author wants a PNG of what's on screen. | | ||
| | `open-slide export` | Headless Chromium | `page.screenshot()` via Playwright | CI, batch, or any slide whose CSS strains the rasterizer. | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| { | ||
| "title": "CLI", | ||
| "pages": ["overview", "init", "dev", "build", "preview", "sync-skills"] | ||
| "pages": ["overview", "init", "dev", "build", "preview", "export", "sync-skills"] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Keep the changeset description user-facing.
“Via headless Chromium” is an implementation detail. Use a concise description of the observable capability instead.
Suggested wording
📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines