mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
There was no way to turn a presentation into an animated GIF. file.export now writes one: format
`gif` (or a .gif path), one frame per slide drawn by the slide renderer and shown for
secondsPerSlide, encoded with the `image` crate's GIF encoder (already a dependency, no new
crates) with a 256-colour palette per frame: exact when a slide has 256 colours or fewer,
NeuQuant otherwise. Slides are composited onto white.
Options: width (small 640, medium 960 (default), large 1280, extraLarge 1920, or 16-3840 px; the
height follows the slide's aspect ratio, no side over 3840), secondsPerSlide (0.1-600, default 5,
written in GIF hundredths), loop (forever by default, or once: no NETSCAPE2.0 block), slides (a
list of indices, in play order) and includeHidden (hidden slides are skipped by default).
Out-of-range or wrongly typed values, NaN and infinities are BadParams errors, never panics; at
most 500 frames and 200 million pixels in total, checked before anything is rendered.
- Engine: crates/engine/src/gif_export.rs (options, frame list, size, encoder);
cmd::file::gif_bytes, save_bytes("gif"), format_for_path(".gif").
- UI: the Export… dialog (Command palette; the app has no File menu yet) gets "Animated GIF (.gif)" with Size, Seconds per slide, From/To slide,
Loop forever and Include hidden slides; an invalid range or value keeps the dialog open with a
message. The web build downloads the bytes (image/gif).
- CLI: convert IN OUT.gif, run --export OUT.gif --export-params JSON; usage lists the format and
options.
- MCP: the export tool lists gif and width/secondsPerSlide/loop (slides and includeHidden now
apply to PDF and GIF).
- Docs: file-format parity GIF row, gaps (video export remains), parity checklist row M -> P
(P2 breadth 36% -> 37%, overall 79%), control protocol, MCP, README, ROADMAP progress log.
Transitions and build animations are not animated in the GIF (tracked with video export).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.6 KiB
2.6 KiB
Control protocol
Start the app with --control <port>. The server listens on 127.0.0.1 only and speaks JSON
lines: one request object per line, one reply per line.
{"id": 1, "method": "engine.execute", "params": {"command": "shape.insert", "params": {"preset": "roundRect", "rect": [100, 100, 240, 120], "text": "Hello"}}}
{"id": 1, "ok": true, "result": {"id": 258}}
Coordinates are slide points (1/72 inch; a 16:9 slide is 960 × 540) unless noted.
| Method | Params | What it does |
|---|---|---|
engine.execute / ui.menu.invoke / command |
{command, params} |
Run any command (see engine.commands; deckcraft-cli describe ID documents one) |
engine.commands |
— | Every command: id, label, menu path, shortcut, params doc, enabled / disabled reason |
document.inspect |
— | Slides, sections, layouts, shapes, selection |
ui.inspect |
— | UI state, active tool, window/canvas/slide rects, dialog, Home (start screen over the open presentations), slide show state, documents, perf (fps, render ms) |
ui.tool.select |
{tool, preset?} |
select, text, shape + preset, any preset name, curve, freeform, scribble, pen, highlighter, eraser |
ui.pointer |
{events: [{kind: down|drag|up|move|doubleclick|tripleclick, x, y, mods?}], mods?} |
Drive the active tool in slide points through the same code path as the mouse |
ui.key / ui.text |
{key, shift?, alt?, cmd?} / {text} |
Keyboard input (also drives a running slide show) |
ui.move / ui.click / ui.drag |
screen points | Real egui pointer input — reaches every widget, menu and pane |
ui.set |
{tab?, view?, pane?, notes?, zoom?, brightness?, ruler?, gridlines?, guides?, …} |
UI state (returns the full state) |
ui.dialog.open / ui.dialog.close |
{id, fields?} |
Open or close a dialog by id; fields pre-fills its fields (About: {"tab": "contributors"} or "models") |
ui.screenshot |
{path?} |
PNG of the whole window (base64 when no path) |
ui.render |
{path?, slide?, scale?} |
Render a slide headlessly |
ui.resize / ui.focus |
Window control | |
show.start / show.end |
{from?: slide index, presenter?, reading?} |
Slide show |
app.open / app.save / app.export / app.quit |
{path, …} |
Files (export takes the file.export params, e.g. {"path": "deck.pdf", "layout": "handouts", "perPage": 3} or {"path": "deck.gif", "width": "large", "secondsPerSlide": 3, "loop": false}) |
The MCP server (deckcraft-cli mcp, see mcp.md) wraps the same methods for Claude
and other agents; deckcraft-cli app METHOD [JSON] sends one request from a shell.