Introduction
Sliderino is presentation software for the age of agents. It has a simple visual editor. External agents get complete control through a command-line interface (CLI) and a Model Context Protocol (MCP) server.
A presentation is one self-contained .sldr file that you own. The file
holds the slides and their fonts, images and videos. Sliderino needs no
server.
What you can do
- Make slides in the editor with text, shapes, images, groups, tables, videos and shaders.
- Let an agent change the open presentation while you watch. The changes of the agent and your changes go into one undo history.
- See text that overflows its box. The editor shows it, and agents get it
from the
get_diagnosticstool. - Present on the full screen.
- Export the presentation to an editable PowerPoint file.
Where to go next
- Getting started: install Sliderino, open the editor and connect an agent.
- Agent API: the tools that the CLI and the MCP server give.
- PowerPoint export: what the export keeps.
- Vision: what Sliderino is meant to be.
- Changelog: the changes in each release.
Contributors can also read Debug scenes and Releasing.
Sliderino has the MIT license. The source code is on GitHub.
Getting started
Sliderino runs on Linux (x86_64) and macOS (Apple silicon). Shaders need a GPU. Videos need GStreamer.
1. Install Sliderino
Run the installer:
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/tncardoso/sliderino/releases/latest/download/sliderino-installer.sh | sh
The installer puts sliderino in ~/.local/bin.
Sliderino uses GStreamer to read and play videos. Install it:
# Debian, Ubuntu
sudo apt install gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-libav
# macOS
brew install gstreamer
Build from source
To build from source, install a Rust toolchain of version 1.98 or newer and
the development files of GStreamer. On Debian and Ubuntu, these are
libgstreamer1.0-dev and libgstreamer-plugins-base1.0-dev. GPUI also
needs libxkbcommon-dev, libxkbcommon-x11-dev, libxcb1-dev,
libwayland-dev and libfontconfig-dev. Then run:
cargo install sliderino
2. Open the editor
sliderino # the Home screen
sliderino deck.sldr # open a file
The Home screen opens. Press Ctrl+N for a new presentation, or Ctrl+O to
open a .sldr file.
3. Connect your agent
Add the MCP server to your MCP client. For Claude Code:
claude mcp add sliderino -- sliderino mcp
The editor shows each connected client at the top of the window. Then give the agent a task. For example:
Open Sliderino and make a five-slide Q3 review. Put the three KPIs on slide 2, then check for overflow.
4. Use the CLI
The CLI gives the same tools as the MCP server. Each command writes JSON to the standard output.
sliderino info # slides, fonts, undo state
sliderino apply ops.json --label "Add KPIs" # one undo step
sliderino screenshot -o slide.png # render a slide to PNG
sliderino export-pptx deck.sldr -o deck.pptx # editable PowerPoint
sliderino tools # list every tool
Run sliderino --help to see all commands. The Agent API
tells what each tool does.
Agent API
External agents read and change the presentation that is open in the Sliderino editor. They use the Model Context Protocol (MCP) server or the command-line interface (CLI). Both give the same tools.
Each change appears in the editor immediately. The person and the agents share one undo history.
Connect an MCP client
Add the server to the client. For Claude Code:
claude mcp add sliderino -- sliderino mcp
The client starts sliderino mcp and stops it. The editor does not have to
be open when the client starts the server. If no editor is open, call the
open_editor tool. It opens an editor with a new presentation.
A window of Sliderino shows the Home screen or the editor. On the Home
screen, only new_presentation and open_presentation work. The other
tools of the instance fail with no_presentation.
The editor shows each connected client at the top of the window, for example “Claude Code · MCP”. Click it to see the clients.
Give the guide to the agent
The text from “Work with a presentation” to “Errors” on this page is also a
skill for agents, SKILL.md. Sliderino gives it in two ways:
- MCP: the resource
skill://sliderino/SKILL.md. - CLI:
sliderino skillwrites it to stdout.
To install it as a skill of Claude Code:
mkdir -p ~/.claude/skills/sliderino
sliderino skill > ~/.claude/skills/sliderino/SKILL.md
Work with a presentation
Sliderino is a presentation editor. The tools read and change the presentation that is open in the editor. The person sees each change immediately.
- Call
get_basic_infoone time. It gives the slides, the fonts, the images and the revision. If no editor is open, callopen_editorfirst. - Do these steps again until the slides are correct:
- Call
get_screenshotto see the slide. - Call
apply_operationsto change the slide. One call is one undo step. - Call
get_diagnosticsto find text that overflows its box and other problems.
- Call
- Call
save_presentationwhen it is necessary to keep the changes.
Use the CLI
The CLI calls the open editor and writes JSON to stdout. Errors go to stderr
as {"error": {...}}, and the exit code is 1.
sliderino instances # list_instances
sliderino open # open_editor
sliderino info # get_basic_info
sliderino new # new_presentation
sliderino open-file deck.sldr # open_presentation
sliderino save deck.sldr # save_presentation
sliderino export-pptx -o deck.pptx # export_pptx
sliderino export-pptx deck.sldr -o deck.pptx # export_pptx, without an editor
sliderino screenshot -o slide.png # get_screenshot
sliderino shader-video 4 -o shader.mp4 # render_shader_video
sliderino apply ops.json --label "Add title" # apply_operations
sliderino undo # undo
sliderino redo # redo
sliderino tools # the tools and their arguments
sliderino skill # this guide, as SKILL.md
sliderino call get_slide '{"slide": 2}' # any tool
sliderino apply - and sliderino call TOOL - read from stdin.
sliderino deck.sldr opens the file in a new editor window. sliderino
without a file opens the Home screen.
Select an instance
Each editor process is one instance. When one instance is open, all tools use it. When more instances are open, give the process id:
- MCP: the
instanceargument of the tool. - CLI:
--instance PIDor theSLIDERINO_INSTANCEenvironment variable.
If you do not give the process id, the tool fails with several_instances.
The error data lists the instances.
Tools
| Tool | Function |
|---|---|
list_instances | List the open editors. |
open_editor | Start an editor and wait until it accepts calls. |
new_presentation | Show a new presentation in the editor. |
open_presentation | Open a .sldr file in the editor. |
save_presentation | Save the presentation to a .sldr file. |
export_pptx | Export the presentation, or a .sldr file, to an editable PowerPoint file. |
get_basic_info | Get the file, the unsaved state, the revision, the slides, the fonts, the images, the videos and the undo state. |
get_selection | Get the slide, the list of slides, the list of elements and the text that the person selected. |
get_slide | Get the elements of a slide in paint order. |
get_elements | Get elements by id, with the text layout and the overflow. |
find_elements | Find text elements that contain a string. |
get_screenshot | Render a slide to PNG. The default scale is 0.5 (800 × 450). Videos show their first frame. Shaders show their frame at time (default 0). |
get_diagnostics | List text that overflows its box, characters that the font does not have, shaders that do not compile, and videos and shaders that cannot play on this computer. |
render_shader_video | Render the shader fill of an element to an MP4 file. |
list_fonts | List the embedded faces and the families that you can embed. |
apply_operations | Apply a list of operations as one undo step. |
undo | Undo the latest step, from a person or from an agent. |
redo | Redo the latest undone step. |
Tools that do not get a slide argument use the slide that the editor
shows.
Operations
apply_operations gets a list of operations. Each operation is a JSON
object. The op field gives the type of the operation:
[
{"op": "add_slide", "slide": {"id": "$s"}},
{"op": "add_element", "slide": "$s", "element": {
"id": "$title", "frame": {"x": 120, "y": 80, "width": 900},
"text": {"content": "Activation grew faster than signups", "sizing": "auto_height",
"style": {"font": {"family": "Inter", "weight": 600}, "size": 72}}}},
{"op": "set_text_style", "id": "$title", "patch": {"color": "1F4BFF"}}
]
Rules:
- All operations of a call apply, or no operation applies. If an operation
fails, the error data gives its index (from 0) in
op. - One call is one undo step. The
labelargument names the step. - Do not give the ids of new slides and elements. The editor gives the next free id.
- To use a new id in a later operation of the same call, write a reference
such as
"$title"as the id. The result gives the id of each reference inrefs. duplicate_slidecopies a slide and its elements. The copy and its elements get new ids. The copy goes after the slide, or toindexif you give it. To use the id of the copy in a later operation, give a reference inref:{"op": "duplicate_slide", "id": 3, "ref": "$copy"}.remove_slidefails on the last slide of the presentation. Add a slide before you remove it.- The editor embeds each font face that an operation uses. It gets the face
from the fonts of Sliderino or from the fonts of the system. If the
license of the face does not permit embedding, the editor embeds it and
the result has a warning in
warnings.
The operations:
op | Fields |
|---|---|
add_slide | slide (optional, {"id", "elements"}), index (optional; the default is the end) |
remove_slide | id |
move_slide | id, index |
duplicate_slide | id, index (optional), ref (optional) |
add_element | slide, element, parent (optional, a group), index (optional) |
remove_element | id |
move_element | id, parent (optional), index (optional) |
group | children (element ids), id (optional) |
ungroup | id |
set_layer | id, patch |
set_frame | id, frame |
set_text_sizing | id, sizing |
set_text_style | id, patch |
replace_text | id, range ({"start", "end"}, in bytes of the text), text |
set_shape_style | id, patch |
set_line_points | id, from, to |
add_font, remove_font | See “Fonts”. |
add_image, remove_image | See “Images”. |
add_video, remove_video | See “Videos”. |
| Table operations | See “Tables”. |
batch | ops: a list of operations that apply as one operation. |
An element is {"id", "name", "hidden", "locked", "opacity", "frame"} and
one key for its kind: text, group, rectangle, ellipse, line or
table. All fields but the kind are optional. A frame is {"x", "y", "width", "height", "rotation"} in slide units. A new presentation has
slides of 1600 × 900 units. Colors are hex strings, for example
"1A1A1A".
Texts
A text element is {"content", "sizing", "style"}:
contentis the text.\nstarts a new paragraph.sizingisauto_width(the default: the text does not wrap, and the frame follows the text),auto_height(the text wraps at the width of the frame, and the height follows the text) orfixed(the text wraps at the width of the frame, and the height does not change). Text that goes past the bottom of afixedframe is an overflow inget_diagnostics.stylehas these fields. All fields are optional.set_text_stylegets the same fields inpatch.
| Field | Value |
|---|---|
font | {"family", "weight", "italic"}. The default is Inter, weight 400. The weight is 100 to 900. |
size | Font size in slide units. The default is 32. |
line_height | "auto" (the default: the line spacing of the font) or {"percent": 120} (a percentage of the font size). |
letter_spacing | Extra space after each character, as a percentage of the font size. The default is 0. |
align | left (the default), center, right or justify. |
vertical_align | top (the default), middle or bottom. It applies to a fixed frame. |
paragraph_spacing | Extra space after each paragraph, but not after the last, in slide units. The default is 0. |
underline, strikethrough | true or false (the default). |
case | original (the default) or upper. |
color | Hex color. The default is "111111". |
Groups and layers
The elements of a slide are a tree. A group holds its children in paint
order: the last child is on top. get_slide gives the tree, with the
children of each group in group.children.
- All frames are in slide units, also the frames of children. The rotation of a child is its angle on the slide.
- The frame of a group is the box around the frames of its children, in the axes of the rotation of the group. The editor calculates it.
set_frameon a group with the same size and rotation moves the group and its children.set_frameon a group with a different size scales the positions and the boxes of the children. The font sizes do not change. A text withauto_widthsizing keeps its size and only moves.set_frameon a group with a different rotation turns the group and its children around the center of the group, to the angle you give.- A new group has the rotation 0. In
add_element, the rotation of a group only sets the axes of its box. The children do not turn. groupputs elements of one slide into a new group. The group goes to the position of the topmost element.ungroupputs the children of a group in its position and removes the group.move_elementmoves an element into a group or out of a group. The element keeps its frame.groupandungroupread the document after the earlier operations of the call. In abatch, they cannot use elements that the same batch adds.
Use set_layer to set the name, the visibility, the lock and the opacity
of an element:
{"op": "set_layer", "id": 12, "patch": {"name": "Header", "locked": true}}
-
opacityis a number from 0 to 1. The default is 1. It applies to all elements: texts, shapes and groups. The opacity of a group multiplies the opacity of each child. -
A hidden element and the children of a hidden group do not show in the editor, in screenshots or in diagnostics.
-
A locked element, and each element in a locked group, accepts only
set_layer. Other operations fail withoperation_failed. To change the element, set"locked": falsefirst. -
The person can move a group that holds a locked element. The locked element moves with the group.
Shapes
A slide can have rectangles, ellipses and lines. Add them with
add_element:
[
{"op": "add_element", "slide": 1, "element": {"id": "$card",
"frame": {"x": 100, "y": 100, "width": 400, "height": 240},
"rectangle": {"corner_radius": 16,
"fill": {"linear_gradient": {"angle": 90, "stops": [
{"position": 0, "color": "1F4BFF"}, {"position": 1, "color": "7FD4FF"}]}},
"stroke": {"color": "111111", "width": 2, "dash": "dashed"}}}},
{"op": "add_element", "slide": 1, "element": {
"frame": {"x": 600, "y": 120, "width": 200, "height": 200},
"ellipse": {"fill": "none", "stroke": {"width": 6}}}},
{"op": "add_element", "slide": 1, "element": {
"line": {"from": {"x": 100, "y": 500}, "to": {"x": 400, "y": 600}, "end": "triangle"}}}
]
- An ellipse touches the four sides of its frame. A square frame gives a circle.
- A new rectangle or ellipse has a light gray fill and no stroke.
- The frame of a line has a height of 0. The line goes from the left end to the right end of the frame. The rotation of the frame is the angle of the line. If you give a frame with a height, the line is the horizontal center axis of that frame.
- To place a line by its ends, give
fromandtoinstead of a frame. To move the ends later, useset_line_points.get_elementsgives the ends of a line inpoints. - When a group changes size, the ends of each line in the group move with the group. The stroke width and the corner radius do not change.
Fill (rectangles and ellipses):
| Fill | Fields |
|---|---|
"none" | |
{"solid": {..}} | color, opacity (optional, 0 to 1) |
{"linear_gradient": {..}} | angle (degrees, clockwise; 0 goes from left to right), stops |
{"radial_gradient": {..}} | center (optional, {"x": 0.5, "y": 0.5}), radius (optional, {"x": 0.5, "y": 0.5}), stops |
- A gradient has 2 to 10
stops. A stop is{"position": 0.5, "color": "FFFFFF", "opacity": 1}. The positions go from 0 to 1, in increasing order. - A gradient turns with its shape. The angle of a linear gradient is in the axes of the shape.
- The
centerand theradiusof a radial gradient are fractions of the frame. The radius 0.5 touches the sides of the frame.
Stroke (all shapes):
| Field | Value |
|---|---|
color | Hex color. The default is "111111". |
opacity | 0 to 1. The default is 1. |
width | Slide units, more than 0. The default is 4. |
dash | solid, dashed or dotted. The default is solid. |
- The stroke is on the center of the outline.
- A rectangle or an ellipse without
strokehas no stroke. A line always has a stroke.
Other fields:
corner_radius(rectangles): the radius of all corners, in slide units. A radius larger than half of the shorter side shows as half of the shorter side.startandend(lines): the arrowhead at each end. Usenone,triangle,arrow,diamondorcircle. To set the size, use{"kind": "triangle", "size": "large"}. The sizes aresmall,medium(the default) andlarge: 2, 3 and 5 times the stroke width.
Use set_shape_style to change the style. Give only the fields to change:
{"op": "set_shape_style", "id": 12, "patch": {"stroke": {"width": 8}, "end": "arrow"}}
- In
stroke, give only the stroke fields to change."stroke": nullremoves the stroke of a rectangle or an ellipse. - A field that the shape does not have makes the operation fail. For
example, a line has no
fill.
Tables
A table is a grid of text cells. Add it with add_element:
[
{"op": "add_element", "slide": 1, "element": {"id": "$sales",
"frame": {"x": 100, "y": 100},
"table": {"cells": [["Fruit", "Qty"], ["Apples", "3"], ["Pears", "12"]],
"text": {"size": 28}, "fill": {"solid": {"color": "F4F6FA"}}}}},
{"op": "set_cell_style", "id": "$sales", "rows": {"start": 0, "end": 1},
"fill": {"solid": {"color": "1F4BFF"}}, "text": {"color": "FFFFFF"}},
{"op": "set_borders", "id": "$sales", "rows": {"start": 0, "end": 1},
"sides": "bottom", "stroke": {"width": 3, "opacity": 1}}
]
Size:
- The text in a cell does not wrap.
\nstarts a new line. - Each column is as wide as its widest cell, and each row is as tall as its
tallest cell, plus
padding(default 12) on each side. A column is at least 2 font sizes wide. widthandheightare"auto"(the default) or{"fixed": 400}. A fixed axis is at least that size: the extra space goes to all columns (or rows) in equal parts. The table is larger when its content needs more.set_framewith a new width or height fixes that axis. A size that is not larger than the content sets the axis back to"auto".set_table_sizingsetswidthandheightdirectly.- When a group changes size, a table in it gets the new size as a fixed size. The font size does not change.
Cells:
- A cell is a text, or an object:
{"content": "3", "fill": {..}, "text": {"align": "right"}, "span": {"rows": 1, "columns": 2}}. textof the table is a style patch over the table text style: Inter 24,vertical_alignmiddle.textof a cell is a style patch over the table text style.fillof the table is the fill of each cell. The default is"none". Thefillof a cell replaces it. Cells cannot show videos or shaders.- Rows and columns count from 0. A range is
{"start": 0, "end": 2};endis not in the range.rowsorcolumnsthat you do not give mean all rows or all columns.
Merged cells:
- The top-left cell of a merge (the anchor) has a
spanand holds the text. The other cells of the merge are empty and are not drawn. merge_cellsmerges a range. The texts that are not empty become the lines of the anchor.split_cellsplits the merge that holds a cell. The text stays in the anchor.- A range always grows to hold complete merges.
- Inserted rows or columns in a merge make the merge larger. Removed rows or
columns make it smaller. An operation that cuts a merge in two fails
(
move_rowsandmove_columns).
Borders:
strokeof the table is the stroke of each edge. The default is{"color": "111111", "opacity": 0.4, "width": 2}."stroke": nulldraws no grid.set_borderssets the edges of a range thatsidesgives:all,outside,inside,inside_horizontal,inside_vertical,top,bottom,leftorright.strokeis a stroke (only the fields that change from the table stroke),null(no line) or"inherit"(the table stroke again).- Two neighbor cells share one edge.
Operations:
op | Fields |
|---|---|
set_cell_text | id, row, column, text, range (optional; bytes of the text to replace) |
set_cells | id, row (default 0), column (default 0), values (rows of texts). The table grows when the values do not fit. |
insert_rows, insert_columns | id, index (the new rows go before it), count (default 1). They copy the style of the row or column before them. |
remove_rows, remove_columns | id, index, count (default 1). A table keeps at least one row and one column. |
move_rows, move_columns | id, index, count (default 1), to (the rows go before this row) |
merge_cells | id, rows, columns |
split_cell | id, row, column |
set_table_style | id, text, fill, stroke, padding (all optional) |
set_cell_style | id, rows, columns, fill (null: the table fill), text, reset_text (removes the text overrides first) |
set_borders | id, rows, columns, sides, stroke |
set_table_sizing | id, width, height |
- The operations in one
batchsee the changes of the operations before them. get_elementsaddslayoutto a table:columnsandrows(their sizes) andmissing_glyphs.find_elementsfinds the text of table cells. A match in a table addscell({"row": 1, "column": 0}).
Fonts
A presentation holds its fonts. The editor embeds an installed face when an
operation uses it. To use a font that is not installed, add its file with
add_font:
[
{"op": "add_font", "path": "/home/me/fonts/Brand-Regular.ttf"},
{"op": "add_font", "path": "/home/me/fonts/Brand-Bold.ttf"}
]
- Sliderino supports TTF, OTF and TTC files. It does not support WOFF and WOFF2 files. Convert them to TTF or OTF first.
- Give
pathordata. The CLI and the MCP server read the file ofpathand send its bytes to the editor. A relative path starts from the folder where the client runs.datais the file in base64. add_fontadds all faces of the file. To add only one face of a TTC file, also giveface.- The editor does not examine the license of a font file. You must have the license to use the font.
- If other fonts already use the family name of the file, the editor gives
the family a new name, for example
Brand (2). The result has a warning inwarnings. A later file of the same family goes intoBrand (2)if that family does not already have the same weight and slant. - The result gives the faces of the file in
fonts, with their family names in the presentation. Use these names in the text style. - If the presentation already holds the same face with the same bytes,
add_fontdoes not add it again. - A variable font adds one face: its default instance. The result has a
warning in
warnings. remove_fontwithfaceremoves a face that no text uses.remove_fontwithfamilyremoves all faces of the family. The texts that use the family change to the nearest face of Inter. You cannot remove Inter while texts use it.
Images
A presentation holds its images. Add an image with add_image, then use it
as the fill of a rectangle or an ellipse:
[
{"op": "add_image", "id": "$logo", "path": "/home/me/logo.png"},
{"op": "add_element", "slide": 1, "element": {
"frame": {"x": 100, "y": 100, "width": 320, "height": 180},
"rectangle": {"corner_radius": 12, "fill": {"image": {"id": "$logo", "fit": "cover"}}}}}
]
- Sliderino supports PNG and JPEG images. An image can have at most 16384 pixels on a side and 64 MiB.
- Give
pathordata. The CLI and the MCP server read the file ofpathand send its bytes to the editor. A relative path starts from the folder where the client runs.datais the file in base64. - If the presentation already holds the same bytes,
add_imagedoes not add them again. The reference names the image that is already there. - An image fill is
{"image": {"id", "fit", "opacity"}}.fitiscover(the default: the image covers the shape and the parts outside are cut off),contain(all of the image shows in the shape) orstretch(the image fills the frame and its proportions change). - A JPEG keeps its EXIF orientation.
widthandheightinget_basic_infoare the size of the upright image. Use them to give the frame the proportions of the image. remove_imageremoves an image that no fill uses. An image that a fill uses stays in the presentation after you delete the element, so that undo can restore the element.
Videos
A presentation holds its videos. Add a video with add_video, then use it
as the fill of a rectangle or an ellipse:
[
{"op": "add_video", "id": "$clip", "path": "/home/me/demo.webm"},
{"op": "add_element", "slide": 1, "element": {
"frame": {"x": 160, "y": 90, "width": 1280, "height": 720},
"rectangle": {"fill": {"video": {"id": "$clip", "start": "on_click", "loop": false}}}}}
]
- Sliderino keeps each video as an MP4 file with H.264 video and AAC audio. PowerPoint and web browsers can play this format. If the file has an other format, the editor changes it to MP4 before it applies the operations. This can take a long time.
- A video can have at most 512 MiB.
- Give
pathordata, as foradd_image. - If the presentation already holds the same bytes,
add_videodoes not add them again. - A video fill is
{"video": {"id", "fit", "opacity", "start", "loop", "muted"}}.fitis the same as for images.startisauto(the default) oron_click.loopistrueby default.mutedisfalseby default. - A presentation plays the videos. A screenshot and a PDF show the first frame of the video.
remove_videoremoves a video that no fill uses.- Sliderino uses GStreamer to read, change and play videos. If a GStreamer
plugin is missing,
add_videofails andget_diagnosticsgives amedia_error.
Shaders
A shader fill draws a fragment shader in the shape. Write the shader in GLSL as on Shadertoy:
{"op": "set_shape_style", "id": 4, "patch": {"fill": {"shader": {
"source": "void mainImage(out vec4 fragColor, in vec2 fragCoord)\n{\n vec2 uv = fragCoord / iResolution.xy;\n fragColor = vec4(uv, 0.5 + 0.5 * sin(iTime), 1.0);\n}\n",
"duration": 6}}}}
- The shader must have
void mainImage(out vec4 fragColor, in vec2 fragCoord).fragCoordis in pixels from the bottom-left corner of the shape box. - The shader can use
iResolution,iTime,iTimeDelta,iFrame,iFrameRate,iMouse(always 0),iDate(always 0),iChannel0andiChannelResolution. The shader has one pass. channel0gives an image of the presentation (an id or a"$name"ofadd_image) foriChannel0. As on Shadertoy,texture(iChannel0, fragCoord / iResolution.xy)shows the image upright.- The alpha of
fragColorhas no effect: the shader is opaque. Useopacityto show what is below. - Without
source, the fill gets the default shader of Shadertoy. durationis from 1 to 60 seconds (default 10). A shader withloopstarts again afterduration. A shader withoutloopstops atduration.startis the same as for videos.- PPTX gets the shader as a video of
durationseconds. Userender_shader_videoto make this video and examine it. A screenshot and a PDF show the shader at time 0;get_screenshotwithtimeshows it at an other time. - A shader that does not compile shows a gray box.
get_diagnosticsgives the problem asshader_errorwith thelineof the source and themessage.
Files
A .sldr file holds the presentation with its fonts, images and videos. It
is a zip archive:
presentation.jsonholds the format number, the slide size, the slides, the next ids and the list of the embedded files.fonts/,images/andvideos/hold the embedded files as they were added.
Use these tools:
save_presentation {path?}writes the file. Withoutpath, it writes the file of the presentation. If the presentation has no file, the tool fails withno_file. Withpath, it replaces the file if it exists, and the path becomes the file of the presentation. It adds.sldrwhen the path has no extension.open_presentation {path, discard?}shows the file in the editor.new_presentation {discard?}shows a new presentation in the editor.
A relative path starts at the working folder of the client.
open_presentation and new_presentation fail with unsaved_changes when
the open presentation has changes that are not saved. Save them with
save_presentation, or give discard: true to lose them.
get_basic_info gives file (the path, or null) and unsaved (true when
the presentation changed after it was opened or saved).
Export to PowerPoint
export_pptx {path, file?} writes an editable PowerPoint file (.pptx).
- Without
file, the tool exports the presentation in the editor. The export includes the changes that are not saved. - With
file, the client exports that.sldrfile. No editor is necessary. - The tool adds
.pptxwhenpathhas no extension. It replaces the file if it exists. - A relative path starts at the working folder of the client.
The result gives file, bytes and warnings. Each warning has slide,
element (or null) and message. A warning tells what PowerPoint shows
differently or does not show, for example:
- Text that goes past the bottom of its box.
- Characters that the font does not have.
- A font that the deck cannot embed, because of its license or because it has CFF outlines.
- Two faces of one family that use the same PowerPoint style (see below).
- A turned table. PowerPoint cannot turn a table, so the table becomes a group of shapes.
- A video or shader fill with an opacity less than 1. PowerPoint plays videos opaque.
A warning does not stop the export.
The deck keeps the position, the size and the rotation of each element. Texts keep the line breaks of Sliderino. The deck embeds the fonts. PowerPoint has four styles for each family: regular, bold, italic and bold italic. A face with a weight of 600 or more uses the bold style. The deck embeds the face that the texts use most for each style.
Videos and shaders play in the slide show. A shader becomes a video of its
duration, at 30 frames per second. The export of shaders takes some
seconds. docs/pptx-export.md gives all the rules.
Presentations
Click Present in the editor to show the slides on the full screen, from the slide that the editor shows:
- The
autovideos and shaders of a slide start when the slide shows. - A click, →, Space or Page Down starts the next
on_clickfill of the slide. The fills start in layer order, from the bottom. When allon_clickfills started, the same keys go to the next slide. - A click on a video or a shader that started pauses it or plays it again.
- ← and Page Up go to the previous slide. Esc stops the presentation.
- D shows or hides the performance figures: the frames per second of the screen, the new pictures and the decoded video frames per second, the skipped shader frames per second, and the time of each step of a frame.
Rotation
frame.rotationis an angle in degrees. A positive angle turns the element clockwise around the center of its frame.- The rotation is the final angle, not a change. If you send the same
set_frametwo times, the result is the same as one time. - The editor keeps the angle between -180 (not included) and 180. For example, 270 becomes -90.
x,y,widthandheightare the frame before it turns.- When a rotated text changes size to fit its content, its rotated top-left corner stays in position.
- For a rotated element,
get_elementsandfind_elementsalso givebounds: the unrotated box around the element, in slide units. Use it to find overlaps and elements that go past the edge of the slide. - If a child turns by an angle that is not a multiple of 90° in its group, a resize of the group that is not proportional changes the child approximately. A box cannot stretch along an axis that is not its own.
Detect changes by other clients
Each change increments the revision of the document. All tools that read or
change the document give the revision.
To make sure that you change the version that you read, give
base_revision to apply_operations, undo or redo. If the document
changed after that revision, the tool fails with stale_revision and changes
nothing. The error data gives the current revision. Read the document
again, then send the change again.
Errors
| Code | Cause |
|---|---|
no_instance | No editor is open. |
several_instances | More than one editor is open and the call gives no instance. |
unknown_instance | No editor has the given process id. |
invalid_arguments | The arguments do not match the schema of the tool. |
unknown_slide, unknown_element | The id does not exist. |
render_failed | The editor cannot render the slide or the shader video. |
operation_failed | An operation cannot apply. data.op gives its index. |
operation_failed with “no cell at row R, column C” | The cell is not in the table, or a merge covers it. |
operation_failed with “the edit would cut a merged cell” | Move the complete merge, or split it first. |
stale_revision | The document changed after base_revision. |
no_presentation | The window shows the Home screen. Call new_presentation or open_presentation. |
unsaved_changes | The open presentation has unsaved changes and the call gives no discard: true. |
no_file | save_presentation gets no path and the presentation has no file. |
cannot_open | open_presentation cannot read the file. |
io | The editor cannot write the file. |
connection_failed | The editor closed the connection. |
closed | The editor closed during the call. |
unknown_tool | No tool has the given name. |
open_failed | open_editor cannot start an editor, or the editor does not accept calls in 20 seconds. |
export_failed | export_pptx cannot make the PowerPoint file. |
protocol, invalid_request | The client and the editor cannot read the messages of the other. Use the same version of Sliderino for both. |
Follow the agent
By default, agent changes do not change the slide or the selection that the person sees. To show each agent change, click the agent status at the top of the window and set Follow agent. The editor then shows the slide of each change and selects the changed element.
How it works
Each editor opens a Unix socket and writes a description file:
$XDG_RUNTIME_DIR/sliderino/<pid>.sock$XDG_RUNTIME_DIR/sliderino/<pid>.json
If XDG_RUNTIME_DIR is not set, the directory is
$TMPDIR/sliderino-<uid>. Only the user can read the directory and the
socket. Clients remove the files of editors that stopped.
sliderino mcp and the CLI send one JSON message on each line of the
socket. Start the editor with sliderino --no-api to not open the socket.
The API works on Linux and macOS only.
PPTX export
This document tells how Sliderino writes a presentation as a PowerPoint
file (.pptx), where the deck is different from the editor, and how to
check the export. The code is in src/pptx/.
Goal
The deck must look like the preview and stay editable in PowerPoint. Each element keeps its position, its size, its rotation and its paint order. A text box stays a text box, a table stays a table and a group stays a group, when PowerPoint can show them correctly.
Units
A slide unit is 1/120 inch. The 1600 × 900 slide is the 13.333 × 7.5 inch (16:9) slide of PowerPoint.
| Sliderino | DrawingML |
|---|---|
| 1 slide unit | 7620 EMU |
| 1 slide unit of font size | 0.6 point |
| Rotation in degrees, clockwise | rot in 60 000ths of a degree |
| Opacity 0 to 1 | alpha in 100 000ths |
Elements
| Element | Deck |
|---|---|
| Rectangle | p:sp with rect, or roundRect when it has a corner radius |
| Ellipse | p:sp with ellipse |
| Line | p:cxnSp with line, on the horizontal center axis of the frame |
| Text | p:sp text box (txBox) |
| Group | p:grpSp with the rotation of the group |
| Table | p:graphicFrame with a:tbl; a group of shapes when it is turned |
| Video or shader fill | p:pic that plays an MP4 file, with a poster |
The deck does not include hidden elements.
Opacity multiplies into each color, as in the CPU renderer. A group with opacity 0.5 makes each of its shapes 50% transparent. It does not make the group one transparent layer.
Groups
A group keeps its rotation. Its children are in the axes of the group, so PowerPoint can turn and move the group as one item.
Shapes
- Strokes are on the center of the outline, with flat ends and miter joins.
- Dashes have the lengths of Sliderino: dashes are 4 stroke widths long
with gaps of 3, and dots are 1 stroke width long with gaps of 1
(
a:custDash). - Arrowheads use the PowerPoint types:
triangle,arrow,diamondandoval. The sizes small, medium and large aresm,medandlg. They are 2, 3 and 5 stroke widths, as in Sliderino. - A linear gradient uses
a:linwith the angle of Sliderino,scaled="0", and turns with the shape. - A radial gradient uses a circle path. The focus (
a:fillToRect) is the center of the gradient. The deck moves the stops so that they fall where the Sliderino ellipse puts them. - An image fill stretches over the rectangle that its fit gives, as
offsets from the box of the shape (
a:fillRect). Cover gives negative offsets, contain gives positive offsets. The shape clips the image. - The deck writes each image once. A JPEG that EXIF turns is encoded again upright, because PowerPoint does not read the EXIF orientation.
Texts
The deck keeps the line breaks of Sliderino:
- Each paragraph (
\n) is onea:p. - Each line of the layout ends with a line break (
a:br). The body does not wrap (wrap="none") and does not fit its text (a:noAutofit). - A justified paragraph is different: it is one run that wraps in its box
(
wrap="square"), because PowerPoint does not justify a line that ends with a line break. - The spaces at the end of a line are removed.
The deck writes the style of the text on each run: size, bold, italic,
underline, strikethrough, cap="all" for upper case, character spacing
(spc) and color. Lines have an exact height (a:spcPts). The Auto line
height becomes the height that Sliderino gives it from the font metrics.
Paragraph spacing is the space after each paragraph, but not the last one.
The insets of the text box are 0.
The first baseline must be where Sliderino puts it. A viewer puts the baseline of an exact line at 0.2 × the font size above the bottom of the line, for each font. Sliderino puts the extra height of the line half above and half below the ascent and the descent of the font. The deck moves the text box by the difference, along the rotation of the box.
Fonts
The deck embeds the fonts (p:embeddedFontLst) as Embedded OpenType files
(.fntdata, version 2.1, not compressed), as PowerPoint writes them.
PowerPoint has four styles for each family: regular, bold, italic and bold italic. A face with a weight of 600 or more uses the bold style. For each style, the deck embeds the face that the texts use most. The embedded file has the names and the weight class of that style, so PowerPoint finds it. Thus a SemiBold text shows the SemiBold face. When two faces of a family use one style, the other face shows as the embedded face, and the export gives a warning.
The deck does not embed:
- A font whose license does not permit embedding (OS/2
fsType). - A font with CFF outlines. PowerPoint embeds only TrueType outlines.
The export gives a warning for each of them.
Tables
A table is an editable PowerPoint table:
- The column widths and the row heights are those of the Sliderino layout.
- Merged cells use
gridSpan,rowSpan,hMergeandvMerge. - Each cell has its fill, its four borders, its vertical alignment and its text. The deck uses no table style.
- The margins are the padding of the table. The top and bottom margins also move the text by the baseline difference (see “Texts”).
- Sliderino makes a column as wide as its widest line. A viewer that measures the text a little wider wraps it. Thus the margin where the lines end is smaller by 0.25 × the font size, but not more than the padding.
PowerPoint cannot turn a table. A turned table becomes a group of its cell fills, its borders and its texts, and the export gives a warning.
Videos and shaders
A shape with a video or a shader fill becomes a picture (p:pic) that
plays an MP4 file. The first frame is the poster.
- Cover crops the picture (
a:srcRect). The picture has the geometry and the outline of the shape. - Stretch fills the box of the shape.
- Contain makes the picture the size of the video in the box. The outline of the shape is a shape of its own.
- A shader becomes an MP4 file (H.264, no sound) from time 0 to its
duration, at 30 frames per second. Without a GPU, the shape has no fill, and the export gives a warning.
The slide timing (p:timing) starts the auto videos with the slide, all
together. Each on_click video starts on its own click, in layer order.
Videos that loop repeat until the slide ends. Muted videos have no sound.
PowerPoint plays videos opaque. When a video or shader fill has an opacity less than 1, only the poster is transparent, and the export gives a warning.
Known differences
These differences are in the design of the deck:
| Feature | Difference |
|---|---|
| Radial gradient that is not a circle on the slide | The deck uses a circle with the mean of the two radii. |
| Radial gradient larger than the half diagonal of its box | The colors past the half diagonal show as the color at the half diagonal. |
| Thick horizontal table border | Sliderino extends it by half the stroke width at each end. PowerPoint does not. |
| Contained video in an ellipse or a rounded rectangle | The video shows in a rectangle. |
| Transparent video or shader | The video plays opaque. |
| Turned table | A group of shapes, not an editable table. |
Assumptions not verified in PowerPoint
The export follows ECMA-376 and the files that PowerPoint writes. The rules
below were measured in LibreOffice 26.8 only. Check them in PowerPoint when
you can. Use sliderino-debug export-pptx --guides: the blue guides show
where Sliderino puts the text frames and the baselines.
- The baseline of a line of exact height is 0.2 × the font size above the bottom of the line. LibreOffice does this for Inter, Liberation Serif and Noto Sans, whose descents are 0.24, 0.22 and 0.29 of the size.
- A circle path gradient is a circle around its focus with a radius of
half the diagonal of the box. LibreOffice does not use
a:tileRect. - PowerPoint reads uncompressed EOT fonts in
.fntdataparts, and finds a face by the family and style names in the file. - PowerPoint turns an image fill with its shape (
rotWithShape="1"). LibreOffice does not. - PowerPoint applies the transparency of a table border. LibreOffice does not.
- PowerPoint crops, clips, outlines and turns a video picture. LibreOffice shows the whole video in its box, without rotation.
Check the export
docs/debug-scenes.md gives the sliderino-debug commands for each step:
export-pptx, pptx-dump, pptx-check, pptx-roundtrip, pptx-render
and pptx-compare.
The tests are in src/pptx/parity_tests.rs:
cargo test pptxexports the debug scenes, reads each deck again (inspect.rs) and compares each shape with its element (parity.rs).cargo test -- --ignored pptxrenders each deck with LibreOffice and compares it with the CPU renderer. It also validates each deck against the Open XML schema. These tests need LibreOffice,pdftoppmanddotnet.
Sliderino
Sliderino is presentation software for the agents age: a simple visual editor with complete programmatic control. People compose slides directly, and external agents work through CLI and MCP interfaces. Presentations are self-contained files that users own.
This document defines the intended product for contributors and future implementers. It describes product commitments, not features already available or a technical implementation plan.
Compose slides with a few primitives
Sliderino provides a what-you-see-is-what-you-get (WYSIWYG) editor inspired by design tools’ direct manipulation and small set of composable primitives. Authors build slides from text, images, basic shapes, and groups. They position, resize, align, and stack elements, then apply simple styling.
Sensible defaults help authors produce readable slides without navigating a large collection of specialized tools. The editor remains focused on presentations.
Layouts are fixed arrangements. Authors control element positions and dimensions. Replacing content does not automatically move neighboring elements or shrink text. A two-column slide retains its arrangement when an author changes a paragraph; the author decides whether the new content needs more space.
The preview represents the intended export appearance. When text exceeds its available space, the editor makes the overflow visible without silently changing the design.
Build a reusable component library
Each presentation has its own component library. An author can save an element or group as a component and insert it elsewhere in the presentation. Groups containing components can become larger reusable components.
A component can be a small content unit or a full slide arrangement. Examples include a KPI box containing a number and descriptive text, and a two-column layout containing headings and content areas. Authors build these from the same primitives they use on ordinary slides.
Inserting a component creates an independent, editable copy. Changing a KPI number, description, or appearance in one copy affects neither the library component nor other copies. Editing the library component changes future insertions only.
Components therefore provide reusable starting arrangements. They do not establish live relationships between a library definition and existing slides. This copy behavior also applies to components composed from other components.
Give external agents complete control
Sliderino supplies integrations and APIs for external agents. It does not provide or orchestrate an agent, and authors choose their own automation tools.
Both the command-line interface (CLI) and Model Context Protocol (MCP) expose the same core capabilities:
- Create, inspect, save, and modify presentations.
- Add, reorder, edit, and remove slides.
- Inspect and manipulate visual elements, groups, and styles.
- Create, inspect, edit, and reuse library components.
- Render previews and inspect layout diagnostics.
- Undo changes and export presentations.
These workflows work without opening the graphical editor. Agents can inspect document structure and rendered results to guide their changes. Text overflow is available as machine-readable diagnostic information, so an external agent can detect a problem and adjust the slide.
Share a live editing session
When a presentation is open, changes through CLI or MCP appear immediately in the editor. A person can continue editing a presentation created by an agent, or observe an external agent modifying the current presentation.
Human and agent operations share one chronological undo history. Undo reverses the latest operation regardless of who made it. Related programmatic changes can form one undoable batch, such as repositioning several elements together.
Edits based on stale document state fail clearly instead of overwriting newer work. The technical design must define how clients detect and recover from that failure.
Own and export the presentation
Presentations are local, self-contained files. They carry their images and supported fonts so that opening a presentation on another machine does not require finding missing assets. The supported font set must permit packaging with presentations.
Core editing, rendering, and export work without a hosted service. Choosing a desktop application or a browser-based interface is a later implementation decision.
Sliderino supports three export formats:
- PDF: fixed-layout slides for sharing and printing.
- PPTX: editable slide content, including text, shapes, and images.
- HTML: presentations viewable in a browser.
Export compatibility constrains the editor’s visual feature set. A feature belongs in that set only if all three formats can represent it reliably. Reusable components export as their resulting slide elements; PPTX need not preserve Sliderino’s component-library semantics.
External viewers may render differently, so identical output in every viewer is not a promise. Exports must preserve the intended layout shown in the preview. Text overflow produces a warning but does not block export or trigger automatic resizing.
Keep the initial product focused
The initial product excludes Markdown authoring, automatic layout, linked component instances, cross-presentation libraries, built-in agents, and hosted collaboration.
Technology choices, storage formats, API syntax, synchronization mechanisms, and delivery schedules belong in later specifications.
Recognize success
The vision succeeds when these workflows are possible:
- A person creates and refines a presentation visually.
- An external agent creates, inspects, revises, previews, and exports a presentation through either CLI or MCP.
- A person watches programmatic edits appear and undoes an operation or batch.
- An author saves and reuses a KPI box, a two-column layout, and a composition of components.
- Editing a component copy leaves other copies unchanged; library edits affect future insertions.
- Overflow produces visible and machine-readable warnings without automatic layout changes.
- A presentation opens on another machine with its required assets.
- The same presentation exports to PDF, editable PPTX, and HTML.
Changelog
Unreleased
Fixed
- The editor no longer stops with a crash when it shows slides with very many shapes or text boxes.
[0.3.0] - 2026-09-30
Added
- A skill for agents,
SKILL.md, that tells how to use the Sliderino tools. The MCP server gives it as the resourceskill://sliderino/SKILL.md, andsliderino skillwrites it to stdout. To install it for Claude Code, runsliderino skill > ~/.claude/skills/sliderino/SKILL.md.
Fixed
- The tool descriptions that MCP clients and
sliderino toolsshow now match what the tools do. For example,get_basic_infolists the videos,list_fontstells when the fonts are still loading, andsave_presentationandexport_pptxtell that they add the file extension.
[0.2.0] - 2026-09-28
Added
- The Sliderino website at https://sliderino.embornal.com, with the documentation at https://sliderino.embornal.com/docs/.
Removed
- The Components tab of the left panel and the Insert component button of the toolbar. They did not do an operation.
0.1.0 - 2026-09-28
Added
-
The Table tool. Click to add a table of 3 × 3 cells, or drag to choose the number of rows and columns. A table is always as small as its text: each column is as wide as its widest cell and each row as tall as its tallest cell. Drag a handle to give it more space; the extra space goes to all columns or rows in equal parts.
-
Double-click a cell to edit its text. Tab and Shift+Tab go to the next and the previous cell. Click, Shift+click or drag to select cells; type to replace the text of a cell, and press Delete to clear the selected cells.
-
Insert, delete and move rows and columns from the context menu, the Table section of the properties panel, or the “+” buttons that show next to a selected table. Drag selected rows or columns to move them.
-
Merge and split cells. Copy and paste cells, also to and from spreadsheets. Paste tab-separated text on the canvas to make a new table.
-
Set the text style, the fill and the stroke of a whole table, or of the selected cells only. Choose the sides of the cells that get the stroke.
-
Agents can add and edit tables: see “Tables” in the agent API.
-
Select more than one slide in the Slides tab: Ctrl+click adds or removes a slide, and Shift+click selects a range of slides.
-
Right-click a slide thumbnail to add a new slide, duplicate the selected slides or delete them. When you delete all slides, an empty slide stays. Undo restores the slides and their selection.
-
Drag slide thumbnails to change the order of the slides. When you drag a selected slide, all selected slides move together.
-
Agents can copy a slide with the
duplicate_slideoperation.get_selectiongives the slides that are selected in the Slides tab inslides. -
The Home screen.
sliderinoopens on it. Start a new presentation (Ctrl+N) or open a file (Ctrl+O) there. -
Save presentations as
.sldrfiles, with their fonts, images and videos. Ctrl+S saves, and Ctrl+Shift+S saves to a new file. Open a file from the Home screen, with Ctrl+O in the editor, or withsliderino deck.sldr. -
The editor shows a dot after the name of a presentation with unsaved changes. Before Home, a new or an opened presentation, or closing the window drops these changes, Sliderino asks to save or discard them.
-
Agents can save and open presentations with
save_presentation,open_presentationandnew_presentation(sliderino save,sliderino open-fileandsliderino new).get_basic_infogives the file and the unsaved state. -
Video fills: fill a rectangle or an ellipse with a video. Drop a video file on the canvas, or pick Video in the fill of the Design tab. Videos in other formats than MP4 (H.264 and AAC) change to MP4 when you add them; the top bar shows the progress. Set the fit, the opacity, when the video starts (Auto or On click), Loop and Sound.
-
Shader fills: fill a shape with a GLSL shader written as on Shadertoy (
mainImage,iTime,iResolution,iChannel0and more). Edit the source in the Design tab; the problems of the source show with their line. Give the shader an image asiChannel0, and set its duration, Start and Loop. -
Preview in the Design tab plays the selected videos and shaders on the canvas, without sound.
-
Present shows the slides on the full screen. Videos and shaders start when the slide shows or on a click, in layer order. Click a video or a shader to pause it. Use the arrow keys to go between slides and Esc to stop. Press D to show the frames per second and the time of each step.
-
Agents can add videos with
add_video, fill shapes withvideoandshader, show shaders at a time with thetimeofget_screenshot, and make the MP4 of a shader withrender_shader_video(sliderino shader-video).get_diagnosticsreports shaders that do not compile and videos that cannot play. -
Videos need GStreamer with its base, good and bad plugins, and an H.264 and an AAC encoder (the ugly plugins or libav) to change other formats. Shaders need a GPU.
-
Upload font files (TTF, OTF or TTC). Click the upload button next to the font family to give the font to the selected texts, or drop the files on the canvas. The font goes into the presentation. If an installed or embedded font already has the family name, the uploaded font gets a new name, such as “Roboto (2)”.
-
The family list shows the fonts of the presentation first, under “In this presentation”.
-
The Design tab with nothing selected shows the fonts of the presentation and their size. Remove a font there; its texts change to Inter.
-
Agents can add a font file with
add_fontandpathordata, and remove a font family withremove_fontandfamily. -
Text tool: click to add a text box that grows with its text, or drag to add a box with a fixed size. Text in a narrow box goes onto more lines.
-
Design tab for text: font, style, size, line height, letter spacing, alignment, paragraph spacing, underline, strikethrough, uppercase, color and opacity. Installed fonts are available and go into the presentation.
-
Undo and redo for all changes, and a list of the changes in the History tab.
-
Diagnostics show when text does not fit its box or when the font does not have a character.
-
sliderino-debug rendermakes a PNG image of a slide from a scene file. -
sliderino-debug sceneopens a scene file in the editor and can capture the slide to a PNG image. -
Press Esc while you move or resize a text box to cancel the change.
-
Agents can read and change the open presentation. Add the MCP server with
claude mcp add sliderino -- sliderino mcp, or use thesliderinoCLI (info,screenshot,apply,undo,call). Agent changes appear immediately and go into the same undo history as your changes. Seedocs/agent-api.md. -
The agent status at the top of the window shows the connected agents. Click it and set Follow agent to see the slide and the element of each agent change.
-
Groups. Press Ctrl+G to put the selected elements into a group and Ctrl+Shift+G to ungroup. Click a group to select it, double-click to select an element in it, or Ctrl+click to select the element directly. Press Enter to select the elements of a group and Esc to select the group again.
-
Move a group to move all its elements. Resize a group to scale the positions and boxes of its elements; the font sizes do not change.
-
Select more than one element: Shift+click to add or remove an element, or drag on an empty area to select the elements that the rectangle touches. Move, resize, align and delete act on all selected elements.
-
Hierarchy tab in the left panel: the elements of the slide as a tree, with the top element first. Click a row to select, Shift+click to select a range and Ctrl+click to add a row. Drag rows to change the order or to move elements into and out of groups.
-
Hide and lock elements with the eye and the lock in the Hierarchy tab. Hidden elements do not show in the slide or in exports. Locked elements cannot change until you unlock them.
-
Right-click an element in the Hierarchy tab or on the slide to rename, group, ungroup, hide, lock or delete it.
-
Agents can add groups and use the
group,ungroup,move_elementandset_layeroperations. -
Rotate text boxes and groups. Drag just outside a corner of the selection to turn it; hold Shift to turn in steps of 15°. Near 0°, 90°, 180° and 270° the angle snaps to the exact value. You can also type the angle in the rotation field of the Design tab. With more than one element selected, the field turns each element around its own center.
-
A rotated group keeps its angle: its box and its handles turn with it, and a resize scales it along its own axes.
-
Agents can rotate text and groups with the
rotationofset_frame. The angle is the final angle, so the same operation gives the same result each time.get_elementsandfind_elementsgive theboundsof rotated elements. -
Rectangles, ellipses and lines. Pick a tool and drag on the slide, or click to add a shape of the default size. Hold Shift to draw a square, a circle or a line at a multiple of 45°, and Alt to draw from the center. Drag an end of a selected line to move it.
-
Shapes have a fill: a color, a linear or radial gradient with up to 10 colors, or an image. They have a stroke with a color, a width and a solid, dashed or dotted style. Rectangles can have round corners, and lines can have arrowheads in three sizes at each end.
-
Select several shapes to change their style together. The Design tab shows “Mixed” for a value that differs.
-
Opacity for all elements, in the position section of the Design tab. The opacity of a group applies to all its elements.
-
Images. Click the Image button, drop PNG or JPEG files on the slide, or paste an image. The image goes into the presentation, so the presentation opens on another computer with it. Choose Image as the fill of a shape to show an image in it, and choose how it fits: cover, contain or stretch.
-
Agents can add shapes and images with
add_element,add_image,set_shape_styleandset_line_points. See “Shapes” and “Images” indocs/agent-api.md. -
Export to PowerPoint: choose Export › PowerPoint (.pptx) in the top bar. The deck keeps the place, size and rotation of every element, and stays editable: texts are text boxes with the same line breaks, tables are tables and groups are groups. The fonts go inside the file. Videos and shaders play in the slide show, and start as they do in Sliderino. When PowerPoint shows something differently, such as text that overflows or a turned table, a message tells what.
-
Agents export with the
export_pptxtool, and the CLI withsliderino export-pptx -o deck.pptx. Give a.sldrfile to export it without opening the editor.
Changed
- The editor title bar shows Home instead of the folder. Click it to go back to the Home screen.
open_editoropens an editor with a new presentation, as before. The other tools fail withno_presentationwhen the window shows the Home screen.- The agent tool
get_selectiongives the selected elements as a list. - Text opacity is now the opacity of the element. Agents set it with the
opacityofset_layer, not with the text style. - Shader fills play smoother in a presentation and in the editor preview: the GPU no longer makes the screen wait for a new frame.
Fixed
- Error messages, such as a file that cannot be read, show in the window.
- Text in an embedded font now shows in the editor when an installed font has the same family name, or when the font has no letter “m”.
- Moving and resizing a text box is smooth. Before, the editor stopped responding during the drag.
- The first selection of a text box no longer waits for the system fonts to load.
- The selection rectangle and the outline of each selected element now show on the slide.
Debug scenes
Use sliderino-debug to make an image of a slide without the editor UI.
A scene file gives the changes to make to a new presentation.
The tool is for contributors. It is a cargo example, so it is not in the
releases. Run it with cargo run --example sliderino-debug.
Commands
Render a slide on the CPU:
cargo run --example sliderino-debug -- render --ops debug/scenes/text.json -o out.png
| Option | Default | Function |
|---|---|---|
--ops FILE | (required) | The scene file. |
-o FILE | (required) | The PNG file to write. |
--slide ID | 1 | The slide to render. |
--scale N | 1.0 | Pixels for each slide unit. The slide is 1600 × 900 units. |
--overlay | off | Show the text frames, the line boxes, the baselines and the overflow. |
Open the scene in the editor:
cargo run --example sliderino-debug -- scene --ops debug/scenes/text.json -o shot.png
| Option | Default | Function |
|---|---|---|
--ops FILE | (required) | The scene file. |
-o FILE | none | Capture the slide to this PNG file, then close the editor. Without this option, the editor stays open. |
--slide ID | first slide | The slide to show. |
--select ID | none | Select this element. The capture shows its outline, handles and size. |
--full | off | Capture all of the window, not only the slide. |
The capture needs an X11 display, xdotool and import (ImageMagick).
Each operation of the scene is one step in the History tab.
Both commands write one line for each text box and each table of the slide: the frame, the number of lines, the overflow and the number of characters that the font does not have.
Scene files
A scene file is a JSON list of operations. The op field gives the type of
operation. The operations are the same as the editor uses. The agent API uses
the same format (see agent-api.md).
[
{"op": "add_font", "face": {"family": "Inter", "weight": 600}},
{"op": "add_element", "slide": 1, "element": {
"id": 1, "frame": {"x": 120, "y": 80, "width": 640},
"text": {"content": "Activation grew faster than signups", "sizing": "auto_height",
"style": {"font": {"family": "Inter", "weight": 600}, "size": 64}}}},
{"op": "set_text_style", "id": 1, "patch": {"underline": true}}
]
op | Fields |
|---|---|
add_slide | slide ({"id": 2}, optional), index (optional) |
remove_slide | id |
move_slide | id, index |
add_element | slide, parent (optional; a group of the slide), element, index (optional; the element goes on top) |
remove_element | id |
move_element | id, parent (optional; without it, the slide), index (optional; the element goes on top) |
group | id (optional), children (a list of element ids on one slide) |
ungroup | id |
set_layer | id, patch (name, hidden, locked, opacity; only the fields to change) |
set_frame | id, frame |
set_text_sizing | id, sizing |
set_text_style | id, patch (only the fields to change) |
set_shape_style | id, patch (fill, stroke, corner_radius, start, end; only the fields to change) |
set_line_points | id, from ({"x": 0, "y": 0}), to |
replace_text | id, range ({"start": 0, "end": 5}, in bytes), text |
add_font | face, or path (from the folder of the scene) or data (base64) of a font file |
remove_font | face, or family |
add_image | id (optional), path (from the folder of the scene) or data (base64) |
remove_image | id |
add_video | id (optional), path (from the folder of the scene) or data (base64). Other formats than MP4 with H.264 change to it. |
remove_video | id |
set_cell_text, set_cells, insert_rows, remove_rows, move_rows, insert_columns, remove_columns, move_columns, merge_cells, split_cell, set_table_style, set_cell_style, set_borders, set_table_sizing | See “Tables” in agent-api.md |
batch | ops (a list of operations; all apply or none apply) |
Rules:
- The first slide of a new presentation has the id
1. The ids of new slides and elements are optional. If you do not give an id, the tool gives the next free id. A reference such as"$title"gives a new id a name, and later operations can use that name as the id. - A text can only use a font face that the scene adds with
add_font.add_fontgives only the name of the face. The tool gets the font data from the fonts of Sliderino (Inter) or from the fonts of the system. If the license of the font does not permit embedding, the tool shows a warning. Scenes do not add fonts automatically, but the agent API does. add_fontwithpathadds the faces of a TTF, OTF or TTC file. Seedebug/scenes/custom-font.jsonand “Fonts” inagent-api.md.sizingisauto_width,auto_heightorfixed.- An element has a
text, agroup, arectangle, anellipse, alineor atable. Seedebug/scenes/tables.jsonand “Tables” inagent-api.md. A group haschildren, a list of elements. The last child is on top. Frames of children are in slide units, as all frames. - Shapes have the same fields as in the agent API. See “Shapes” in
agent-api.md. - An element can have a
name,hidden,lockedandopacity(0 to 1). Set them withset_layer."name": nullremoves the name. - In
styleandpatch, all fields are optional:font,size,line_height("auto"or{"percent": 120}),letter_spacing(percent of the size),align(left,center,right,justify),vertical_align(top,middle,bottom),paragraph_spacing,underline,strikethrough,case(original,upper),color("1A1A1A"). To make a text transparent, set theopacityof the element. - If an operation fails, the tool stops and shows the number of the operation (from 0).
The folder debug/scenes has examples: shapes.json shows the shapes,
fills, strokes and arrowheads, images.json shows image fills, and
shaders.json shows shader fills, a shader channel and a shader that does
not compile.
The scene command waits until the editor decodes all images and video
frames and renders all shaders before it captures the slide. The capture
shows the first frame of videos and shaders at time 0.
PPTX export
These commands check each step of the PPTX export. See pptx-export.md for
the rules of the export.
| Command | Function |
|---|---|
export-pptx --ops FILE -o OUT.pptx [--guides] | Export a scene to PPTX without the editor. --guides adds thin blue lines on the text frames and on the baselines of the Sliderino layout. Use it to see where a viewer puts the text. |
pptx-dump FILE [--part PART] | Without --part, list the parts of the file with their size and content type. With --part, show one part as indented XML, for example ppt/slides/slide1.xml. |
pptx-check FILE [--schema] | Check the package: the content types, the relationships, the XML and the shape ids. --schema also validates the XML against the Open XML schema. |
pptx-roundtrip --ops FILE [-o OUT.pptx] | Export a scene, read the deck, and compare each shape with its element. The command fails when they are different. |
pptx-render FILE -o DIR [--scale N] | Render each slide of a PPTX file to PNG with LibreOffice. |
pptx-compare --ops FILE -o DIR [--slide ID] [--scale N] [--threshold F] | Render a slide on the CPU and its deck with LibreOffice. Write reference.png, pptx.png, diff.png (the different pixels in red) and deck.pptx. Show the fraction of different pixels and where the ink of each element moves. With --threshold, fail when more than this fraction of the pixels is different. |
pptx-render and pptx-compare need LibreOffice (soffice) and
pdftoppm. --schema needs dotnet. The first run builds the tool in
tools/pptx-validate and gets the Open XML SDK from NuGet.
Example: check a scene in all the steps.
cargo run --example sliderino-debug -- pptx-roundtrip --ops debug/scenes/text.json -o text.pptx
cargo run --example sliderino-debug -- pptx-check --schema text.pptx
cargo run --example sliderino-debug -- pptx-compare --ops debug/scenes/text.json -o compare --scale 2
The folder debug/scenes/pptx has scenes for the export: groups.json
(groups in groups, turned groups and hidden elements) and media.json
(video fills with each fit and start, and a shader).
The tests check the parity of the export:
cargo test pptxexports the debug scenes and compares each deck with its presentation. It needs no other programs.cargo test -- --ignored pptxalso compares each deck with LibreOffice, pixel by pixel, and validates it against the Open XML schema. Each scene has a limit of different pixels. The test fails when a scene becomes worse.
Releasing
A release starts with a tag. Everything after the tag is automatic: the CI builds one binary for each platform, makes the GitHub release, and sends the crate to crates.io.
Once, before the first release
-
Make the repository public. The installer and the updater download the binaries from the GitHub release without a login. The build of the release gets the Git LFS fonts without a login. Neither works in a private repository.
-
Write one secret in the repository, at Settings, Secrets and variables, Actions:
Secret Where it comes from CARGO_REGISTRY_TOKENcrates.io, at Account Settings, API Tokens, with the scope publish-newfor the first release andpublish-updateafter itGITHUB_TOKENneeds no work, because GitHub gives it to each workflow.
The steps
-
Move the facts of the release into the changelog. In
CHANGELOG.md, the heading## [Unreleased]becomes the version and the day:## [0.2.0] - 2026-08-14Then write a new empty
## [Unreleased]above it. The release notes on GitHub are this section, so what it does not say, the release does not say. -
Write the same version in
Cargo.toml. One command does it, and writesCargo.lock:cargo install cargo-edit # once cargo set-version 0.2.0 -
Run what each change runs:
cargo fmt --check cargo clippy --all-targets -- -D warnings cargo test -
Read what goes to crates.io, without sending it:
cargo publish --dry-run --locked -
Commit, tag and push. The tag is the version with a
vin front of it:git commit -am "release: 0.2.0" git tag v0.2.0 git push && git push --tagsPush the commit first and let the CI pass.
publish-crates.ymldoes not publish a commit that has no green CI.
The number itself follows Semantic Versioning. A change that makes an old command or tool answer in a new way is a major release, even when the code of the change is small. Before version 1.0.0, a minor release can hold such a change.
What the tag starts
| Workflow | What it does |
|---|---|
release.yml | Plans the release, builds each platform on its own runner, and makes the GitHub release with the archives, the checksums and the installer. |
publish-crates.yml | Waits for that release. Then it checks that the tag and Cargo.toml have the same version, and that the checks of ci.yml passed for the tagged commit. Then it sends the crate to crates.io. |
publish-crates.yml runs after the release exists, so a failure at crates.io
leaves the binaries where they are. To send the crate again after such a
failure, start Publish to crates.io by hand from the Actions page and give it
the tag:
gh workflow run publish-crates.yml -f tag=v0.2.0
A crate on crates.io is permanent. A version that went out cannot go out again with different contents, so step 4 is the step to do carefully.
The releases
The releases hold sliderino for Linux (x86_64) and for macOS (Apple silicon).
sliderino-debug is a cargo example, so it is not in the archives and not in
cargo install.
The Linux binary is built on Ubuntu 24.04. It needs glibc 2.39 or newer. Both binaries need GStreamer on the machine of the user. The README says how to install it. The macOS binary is linked to the GStreamer of Homebrew.
The configuration of the release
dist-workspace.toml holds the platforms, the installer and the tools that
each runner installs. .github/build-setup.yml holds the steps that run in the
build job before the build: the libraries and the Git LFS fonts.
.github/workflows/release.yml comes from dist-workspace.toml, so no hand
edits go in it. After a change:
dist generate
git add dist-workspace.toml .github
To read what a release would hold, without a build and without a tag:
dist plan
To make the installer on this machine, which is how to read what it does:
dist build --artifacts=global
To build the archive of this machine, which takes as long as one runner takes:
dist build --artifacts=local
Each of the three writes to target/distrib.
A newer dist
cargo-dist-version in dist-workspace.toml says which version of dist the
CI uses. To move to a newer one, install it and let it write the file again:
cargo install cargo-dist --locked
dist init
dist generate
Read the difference in release.yml before the commit. That file decides which
runner builds each platform, and a new version of dist can move a build to
another image of the operating system.
A release that must not go out yet
A tag such as v0.2.0-rc.1 makes a pre-release on GitHub. dist marks it as
one, so the address releases/latest/download/... still gives the version
before it, and the installer of a user gives the stable release.
publish-crates.yml skips a pre-release, so crates.io does not get it.