Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6c03fb9
Add HTML renderer with animations and deck slideshow
claude Jun 28, 2026
aaccb9e
Migrate HTML document shell to Jinja2 template
claude Jun 29, 2026
80bb633
Simplify HTML exporter: share _fmt, extract _css_gradient, cache temp…
claude Jun 29, 2026
7b8a7de
Fix JS runtime bugs and add HTML slideshow example
claude Jun 29, 2026
1e3f400
Add generated HTML slideshow example output
claude Jun 29, 2026
eb88787
Add production-quality investor deck HTML example (Vela Analytics)
claude Jun 29, 2026
9831e26
Improve HTML export JS runtime performance
claude Jun 29, 2026
828e48c
Play slide transitions in HTML export
claude Jun 29, 2026
2322592
Keep previous slide on screen during HTML transitions, smooth them
claude Jun 29, 2026
06bfa96
Embed fonts in HTML export by default
claude Jun 29, 2026
be120d7
Match raster gradient and shadow geometry in HTML export
claude Jun 30, 2026
a321f17
Render HTML glow/shadow/stroke with SVG filters
claude Jun 30, 2026
3dde183
Simplify HTML exporter transitions and animation registration
claude Jun 30, 2026
5791fe5
Remove slide effects HTML example
sjquant Jun 30, 2026
821d392
Fix HTML export review issues
sjquant Jun 30, 2026
a62c6d5
Simplify HTML export review fixes
sjquant Jun 30, 2026
d653043
Fix non-responsive deck transitions
sjquant Jun 30, 2026
07bf6d7
Fix deck HTML and PPTX animation parity
sjquant Jun 30, 2026
9309760
Prevent duplicate deck animations
sjquant Jun 30, 2026
a631ccf
Share font-face resolution between the SVG and HTML exporters
claude Jul 1, 2026
abe7ad2
Clarify investor deck marker dots
sjquant Jul 1, 2026
5737801
Preserve HTML animation opacity
sjquant Jul 1, 2026
7972ac4
Respect layer opacity during HTML animations
sjquant Jul 1, 2026
b6a717b
Soften HTML box animations
sjquant Jul 1, 2026
0ed1d7c
Keep PPTX animation targets visible
sjquant Jul 1, 2026
48c690e
Initialize PPTX entrance visibility
sjquant Jul 1, 2026
1e3b19d
Fix PPTX after previous animation chaining
sjquant Jul 1, 2026
43d5a1c
Fix PPTX entrance initialization order
sjquant Jul 1, 2026
39bf2fb
Fix PPTX group animation import in Keynote
sjquant Jul 1, 2026
ee3cf0d
Simplify PPTX target effect generation
sjquant Jul 1, 2026
7187964
Simplify PPTX effect node selection
sjquant Jul 1, 2026
d8b6c2a
Fix HTML deck animation review issues
sjquant Jul 1, 2026
018adf8
Extract HTML exporter assets
sjquant Jul 1, 2026
80ebf63
Fix HTML animation review issues
sjquant Jul 2, 2026
aeaeb6e
Simplify HTML runtime tests
sjquant Jul 2, 2026
313ec76
Update examples
sjquant Jul 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 54 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,22 +369,58 @@ jpeg_data_url = canvas.to_data_url(format="JPEG", quality=90)
canvas.render("output.webp", format="WEBP", quality=90)
```

### SVG, PPTX, and PDF renderers
### SVG, HTML, PPTX, and PDF renderers

The same canvas renders to vector and document formats, detected from the file extension:

```python
canvas.render("card.svg") # vector SVG with native shapes, gradients, and selectable text
canvas.render("card.html") # self-contained, animated HTML/CSS page
canvas.render("card.pptx") # PowerPoint slide with editable text boxes and autoshapes
canvas.render("card.pdf") # single-page PDF with native vectors and embedded fonts

svg_markup = canvas.to_svg(embed_fonts=True) # inline @font-face for portable text
pptx_bytes = canvas.to_pptx() # requires quickthumb[pptx]
pdf_bytes = canvas.to_pdf() # requires quickthumb[pdf]
svg_markup = canvas.to_svg(embed_fonts=True) # inline @font-face for portable text
html_doc = canvas.to_html(embed_fonts=True) # standalone document string
pptx_bytes = canvas.to_pptx() # requires quickthumb[pptx]
pdf_bytes = canvas.to_pdf() # requires quickthumb[pdf]
```

Layers the target format can express (backgrounds, gradients, outlines, shapes, text — including wrapping, rich parts, letter spacing, and effects) are exported natively and stay editable; everything else (raster images, blend modes, custom layers) is embedded as pixel-exact PNG fragments rendered by the regular pipeline. See the [export docs](https://sjquant.github.io/quickthumb/exports/) for the full mapping.

### HTML renderer

`canvas.to_html()` produces one **self-contained** HTML document (inline CSS, JS,
fonts, and images — no external files), built on the same layout math as every
other renderer. No optional extra is required.

```python
canvas.render("card.html") # responsive, scale-to-fit page
html_doc = canvas.to_html(responsive=False) # bare fixed-size stage to embed
html_doc = canvas.to_html(embed_fonts=True) # inline @font-face for exact text
```

The composition is a **fixed-size stage that never reflows**, so an HTML export
stays a faithful twin of its PNG/SVG/PDF/PPTX counterpart. With
`responsive=True` (the default) the whole stage is scaled as one unit to fill
the viewport — it adapts to any screen size without rearranging the layout (set
`responsive=False` to emit the bare stage for embedding). Because browsers
rasterize fonts differently from PIL, text placement is a close approximation
rather than pixel-identical (like the PPTX exporter); `embed_fonts=True` inlines
the used fonts for the closest match.

Unlike the other document formats, HTML actually **plays** per-layer
`animation`s: each effect maps to a CSS keyframe driven by a tiny inline JS
runtime that honours the same `on_click` / `with_previous` / `after_previous`
sequencing as PowerPoint (click to advance). A `Deck` renders to a standalone
**slideshow** — `deck.render("slides.html")` — that advances per-layer
animations and then slides with a click or the arrow keys, applying slide
transitions between stages.

```python
deck.render("slides.html") # one self-contained, navigable slideshow
html_doc = deck.to_html() # the document as a string
```

### Decks: multiple images and slides

A `Deck` is an ordered collection of canvases ("slides"). Give the deck a size
Expand All @@ -406,6 +442,7 @@ deck = (

deck.render("deck.pdf") # one multi-page PDF (a page per slide)
deck.render("deck.pptx") # one multi-slide PPTX (a slide per slide)
deck.render("deck.html") # one self-contained, navigable HTML slideshow
deck.render("slides.png") # numbered sequence: slides_01.png, slides_02.png, …

pdf_bytes = deck.to_pdf() # requires quickthumb[pdf]
Expand All @@ -428,12 +465,12 @@ slides larger than the first are clipped by PowerPoint — keep deck slides a
uniform size when targeting `.pptx`. Decks round-trip through JSON with
`deck.to_json()` / `Deck.from_json(...)`, reusing the per-canvas serialization.

### Slide effects (PPTX transitions and animations)
### Slide effects (PPTX and HTML transitions and animations)

When you export to PowerPoint, slides can carry a **transition** (the animation
played as the slide appears) and individual layers can carry **entrance/exit
animations**. These only affect `.pptx` output — every other format ignores
them, so the same spec still renders identically to PNG, SVG, and PDF.
When you export to PowerPoint or HTML, slides can carry a **transition** (the
animation played as the slide appears) and individual layers can carry
**entrance/exit animations**. Raster, SVG, and PDF outputs ignore these effects,
so the same spec still renders identically there.

```python
from quickthumb import Box, Canvas, Deck, Fade, Wipe
Expand Down Expand Up @@ -632,9 +669,9 @@ os.environ["QUICKTHUMB_DEFAULT_FONT"] = "Roboto"
| Theme tokens | Top-level `theme` block with `$theme.path` references in JSON specs |
| Diagnostics | `canvas.diagnose()` and `quickthumb lint`: off-canvas, tiny text, overflow, low contrast |
| Export | PNG, JPEG, WebP, file output, base64, data URLs |
| Document renderers | SVG (`to_svg()`), editable PPTX via `quickthumb[pptx]`, PDF via `quickthumb[pdf]` |
| Document renderers | SVG (`to_svg()`), self-contained animated HTML (`to_html()`), editable PPTX via `quickthumb[pptx]`, PDF via `quickthumb[pdf]` |
| Decks | `Deck` of multiple slides: multi-page PDF, multi-slide PPTX, numbered image sequences, per-slide diagnostics |
| Slide effects | PPTX slide `Transition`s (per-slide or deck default) and per-layer entrance/exit `Animation`s |
| Slide effects | PPTX/HTML slide `Transition`s (per-slide or deck default) and per-layer entrance/exit `Animation`s |
| Serialization | `to_json()` / `from_json()` for built-in layer types and named custom layers |

## Real Example Scripts
Expand All @@ -645,7 +682,7 @@ See the shipped examples in [`examples/README.md`](examples/README.md):
- `examples/youtube_thumbnail_02.py`
- `examples/instagram_news_card.py`
- `examples/launch_announcement.py` — the 0.5 feature set (groups, theme tokens, shapes, SVG, diagnostics) in one JSON spec
- `examples/slide_effects_deck.py` — a multi-slide PPTX `Deck` with slide transitions and per-layer entrance/exit animations
- `examples/investor_deck.py` — an animated investor-style deck exported to both HTML and PPTX

## Gotchas

Expand All @@ -659,9 +696,13 @@ See the shipped examples in [`examples/README.md`](examples/README.md):
- Group children must not set `position`; the group assigns positions (their `align` is also ignored — use `item_align`)
- `svg` layers raise `RenderingError` unless `quickthumb[svg]` (cairosvg) is installed
- `theme` blocks are resolved at parse time; `to_json()` emits resolved values without the `theme` block
- Slide `Transition`s and layer `Animation`s only affect PPTX output; raster, SVG, and PDF renderers ignore them
- Slide `Transition`s and layer `Animation`s affect PPTX and HTML output; raster, SVG, and PDF renderers ignore them
- Transitions live on the `Deck` (default plus per-slide override), not on `Canvas`; a per-slide override wins over the deck default
- Animations are valid on `text`, `shape`, `image`, `svg`, and `group` layers; pass one effect or a list of effects played in order
- HTML export needs no optional extra; the document is fixed-layout and scaled to fit (`responsive=True` by default), never reflowed, so it stays a faithful twin of the PNG/SVG/PDF/PPTX output
- HTML text placement is a close approximation (browsers rasterize fonts differently than PIL); use `embed_fonts=True` for the closest match, available when text uses local font files
- HTML is the only format that plays per-layer `animation`s (and `Deck` slide transitions); a few exotic effects (blinds, checkerboard, wheel, dissolve) fall back to a close CSS analogue
- HTML cannot animate a layer that must be rasterized together with earlier backdrop-dependent content, such as blend-mode or custom layers; move animated layers after that content or remove the backdrop dependency

## Development

Expand Down
18 changes: 8 additions & 10 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ uv run python examples/instagram_news_card.py
uv run python examples/podcast_interview_promo.py
uv run python examples/shorts_cover_agent.py
uv run python examples/launch_announcement.py
uv run python examples/slide_effects_deck.py
uv run python examples/investor_deck.py
```

All examples write their rendered image back into this directory.
Expand Down Expand Up @@ -151,19 +151,17 @@ Shows the quickthumb 0.5 feature set in a single themed JSON spec:

Use it when you want a brandable announcement-card template whose layout survives copy changes, or as a reference spec for LLM-generated layouts.

### `slide_effects_deck.py`
### `investor_deck.py`

Output: `slide_effects_deck.pptx` (plus `slide_effects_deck.png`, a still preview of the opening slide)
Output: `investor_deck.html` and `investor_deck.pptx`

Builds a polished 4-slide PowerPoint `Deck` (title, agenda, hero metric, closing) that shows off slide effects (PPTX-only):
Builds a dark investor-style deck with staged text, metric cards, and chart-like visuals:

- A deck-wide default slide `Transition` with per-slide overrides (`Deck.transition(...)` and `Deck.slide(..., transition=...)`)
- Typed animation effect objects — `Fade`, `Wipe`, `Box`, `Wheel` — each exposing only the options it supports
- Sequencing that leads with the main element (headline, hero number) and then reveals the supporting detail, via `on_click` / `after_previous` triggers
- `group` animations that drive a numbered agenda row or a stat block as a single effect
- Gradient backgrounds and gradient-filled headlines that stay crisp and editable in PowerPoint
- HTML export for browser playback of slide transitions and layer animations
- PPTX export for editable presentation handoff
- Shared composition code that keeps the browser and PowerPoint outputs aligned

Open the `.pptx` in PowerPoint (or Keynote / LibreOffice Impress) and start the slideshow to see the transitions and animations play; the `.png` is just a static render of the first slide, since stills can't show motion.
Use it when you want a realistic animated deck example that exercises both HTML and PPTX output from the same source.

## Assets and Fonts

Expand Down
296 changes: 296 additions & 0 deletions examples/investor_deck.html

Large diffs are not rendered by default.

Binary file not shown.
Loading