Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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_diagnostics tool.
  • Present on the full screen.
  • Export the presentation to an editable PowerPoint file.

Where to go next

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 skill writes 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.

  1. Call get_basic_info one time. It gives the slides, the fonts, the images and the revision. If no editor is open, call open_editor first.
  2. Do these steps again until the slides are correct:
    1. Call get_screenshot to see the slide.
    2. Call apply_operations to change the slide. One call is one undo step.
    3. Call get_diagnostics to find text that overflows its box and other problems.
  3. Call save_presentation when 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 instance argument of the tool.
  • CLI: --instance PID or the SLIDERINO_INSTANCE environment variable.

If you do not give the process id, the tool fails with several_instances. The error data lists the instances.

Tools

ToolFunction
list_instancesList the open editors.
open_editorStart an editor and wait until it accepts calls.
new_presentationShow a new presentation in the editor.
open_presentationOpen a .sldr file in the editor.
save_presentationSave the presentation to a .sldr file.
export_pptxExport the presentation, or a .sldr file, to an editable PowerPoint file.
get_basic_infoGet the file, the unsaved state, the revision, the slides, the fonts, the images, the videos and the undo state.
get_selectionGet the slide, the list of slides, the list of elements and the text that the person selected.
get_slideGet the elements of a slide in paint order.
get_elementsGet elements by id, with the text layout and the overflow.
find_elementsFind text elements that contain a string.
get_screenshotRender 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_diagnosticsList 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_videoRender the shader fill of an element to an MP4 file.
list_fontsList the embedded faces and the families that you can embed.
apply_operationsApply a list of operations as one undo step.
undoUndo the latest step, from a person or from an agent.
redoRedo 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 label argument 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 in refs.
  • duplicate_slide copies a slide and its elements. The copy and its elements get new ids. The copy goes after the slide, or to index if you give it. To use the id of the copy in a later operation, give a reference in ref: {"op": "duplicate_slide", "id": 3, "ref": "$copy"}.
  • remove_slide fails 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:

opFields
add_slideslide (optional, {"id", "elements"}), index (optional; the default is the end)
remove_slideid
move_slideid, index
duplicate_slideid, index (optional), ref (optional)
add_elementslide, element, parent (optional, a group), index (optional)
remove_elementid
move_elementid, parent (optional), index (optional)
groupchildren (element ids), id (optional)
ungroupid
set_layerid, patch
set_frameid, frame
set_text_sizingid, sizing
set_text_styleid, patch
replace_textid, range ({"start", "end"}, in bytes of the text), text
set_shape_styleid, patch
set_line_pointsid, from, to
add_font, remove_fontSee “Fonts”.
add_image, remove_imageSee “Images”.
add_video, remove_videoSee “Videos”.
Table operationsSee “Tables”.
batchops: 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"}:

  • content is the text. \n starts a new paragraph.
  • sizing is auto_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) or fixed (the text wraps at the width of the frame, and the height does not change). Text that goes past the bottom of a fixed frame is an overflow in get_diagnostics.
  • style has these fields. All fields are optional. set_text_style gets the same fields in patch.
FieldValue
font{"family", "weight", "italic"}. The default is Inter, weight 400. The weight is 100 to 900.
sizeFont 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_spacingExtra space after each character, as a percentage of the font size. The default is 0.
alignleft (the default), center, right or justify.
vertical_aligntop (the default), middle or bottom. It applies to a fixed frame.
paragraph_spacingExtra space after each paragraph, but not after the last, in slide units. The default is 0.
underline, strikethroughtrue or false (the default).
caseoriginal (the default) or upper.
colorHex 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_frame on a group with the same size and rotation moves the group and its children.
  • set_frame on a group with a different size scales the positions and the boxes of the children. The font sizes do not change. A text with auto_width sizing keeps its size and only moves.
  • set_frame on 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.
  • group puts elements of one slide into a new group. The group goes to the position of the topmost element.
  • ungroup puts the children of a group in its position and removes the group.
  • move_element moves an element into a group or out of a group. The element keeps its frame.
  • group and ungroup read the document after the earlier operations of the call. In a batch, 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}}
  • opacity is 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 with operation_failed. To change the element, set "locked": false first.

  • 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 from and to instead of a frame. To move the ends later, use set_line_points. get_elements gives the ends of a line in points.
  • 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):

FillFields
"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 center and the radius of a radial gradient are fractions of the frame. The radius 0.5 touches the sides of the frame.

Stroke (all shapes):

FieldValue
colorHex color. The default is "111111".
opacity0 to 1. The default is 1.
widthSlide units, more than 0. The default is 4.
dashsolid, dashed or dotted. The default is solid.
  • The stroke is on the center of the outline.
  • A rectangle or an ellipse without stroke has 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.
  • start and end (lines): the arrowhead at each end. Use none, triangle, arrow, diamond or circle. To set the size, use {"kind": "triangle", "size": "large"}. The sizes are small, medium (the default) and large: 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": null removes 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. \n starts 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.
  • width and height are "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_frame with 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_sizing sets width and height directly.
  • 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}}.
  • text of the table is a style patch over the table text style: Inter 24, vertical_align middle. text of a cell is a style patch over the table text style.
  • fill of the table is the fill of each cell. The default is "none". The fill of a cell replaces it. Cells cannot show videos or shaders.
  • Rows and columns count from 0. A range is {"start": 0, "end": 2}; end is not in the range. rows or columns that you do not give mean all rows or all columns.

Merged cells:

  • The top-left cell of a merge (the anchor) has a span and holds the text. The other cells of the merge are empty and are not drawn.
  • merge_cells merges a range. The texts that are not empty become the lines of the anchor. split_cell splits 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_rows and move_columns).

Borders:

  • stroke of the table is the stroke of each edge. The default is {"color": "111111", "opacity": 0.4, "width": 2}. "stroke": null draws no grid.
  • set_borders sets the edges of a range that sides gives: all, outside, inside, inside_horizontal, inside_vertical, top, bottom, left or right. stroke is 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:

opFields
set_cell_textid, row, column, text, range (optional; bytes of the text to replace)
set_cellsid, row (default 0), column (default 0), values (rows of texts). The table grows when the values do not fit.
insert_rows, insert_columnsid, index (the new rows go before it), count (default 1). They copy the style of the row or column before them.
remove_rows, remove_columnsid, index, count (default 1). A table keeps at least one row and one column.
move_rows, move_columnsid, index, count (default 1), to (the rows go before this row)
merge_cellsid, rows, columns
split_cellid, row, column
set_table_styleid, text, fill, stroke, padding (all optional)
set_cell_styleid, rows, columns, fill (null: the table fill), text, reset_text (removes the text overrides first)
set_bordersid, rows, columns, sides, stroke
set_table_sizingid, width, height
  • The operations in one batch see the changes of the operations before them.
  • get_elements adds layout to a table: columns and rows (their sizes) and missing_glyphs.
  • find_elements finds the text of table cells. A match in a table adds cell ({"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 path or data. The CLI and the MCP server read the file of path and send its bytes to the editor. A relative path starts from the folder where the client runs. data is the file in base64.
  • add_font adds all faces of the file. To add only one face of a TTC file, also give face.
  • 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 in warnings. A later file of the same family goes into Brand (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_font does not add it again.
  • A variable font adds one face: its default instance. The result has a warning in warnings.
  • remove_font with face removes a face that no text uses. remove_font with family removes 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 path or data. The CLI and the MCP server read the file of path and send its bytes to the editor. A relative path starts from the folder where the client runs. data is the file in base64.
  • If the presentation already holds the same bytes, add_image does not add them again. The reference names the image that is already there.
  • An image fill is {"image": {"id", "fit", "opacity"}}. fit is cover (the default: the image covers the shape and the parts outside are cut off), contain (all of the image shows in the shape) or stretch (the image fills the frame and its proportions change).
  • A JPEG keeps its EXIF orientation. width and height in get_basic_info are the size of the upright image. Use them to give the frame the proportions of the image.
  • remove_image removes 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 path or data, as for add_image.
  • If the presentation already holds the same bytes, add_video does not add them again.
  • A video fill is {"video": {"id", "fit", "opacity", "start", "loop", "muted"}}. fit is the same as for images. start is auto (the default) or on_click. loop is true by default. muted is false by default.
  • A presentation plays the videos. A screenshot and a PDF show the first frame of the video.
  • remove_video removes a video that no fill uses.
  • Sliderino uses GStreamer to read, change and play videos. If a GStreamer plugin is missing, add_video fails and get_diagnostics gives a media_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). fragCoord is 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), iChannel0 and iChannelResolution. The shader has one pass.
  • channel0 gives an image of the presentation (an id or a "$name" of add_image) for iChannel0. As on Shadertoy, texture(iChannel0, fragCoord / iResolution.xy) shows the image upright.
  • The alpha of fragColor has no effect: the shader is opaque. Use opacity to show what is below.
  • Without source, the fill gets the default shader of Shadertoy.
  • duration is from 1 to 60 seconds (default 10). A shader with loop starts again after duration. A shader without loop stops at duration. start is the same as for videos.
  • PPTX gets the shader as a video of duration seconds. Use render_shader_video to make this video and examine it. A screenshot and a PDF show the shader at time 0; get_screenshot with time shows it at an other time.
  • A shader that does not compile shows a gray box. get_diagnostics gives the problem as shader_error with the line of the source and the message.

Files

A .sldr file holds the presentation with its fonts, images and videos. It is a zip archive:

  • presentation.json holds the format number, the slide size, the slides, the next ids and the list of the embedded files.
  • fonts/, images/ and videos/ hold the embedded files as they were added.

Use these tools:

  • save_presentation {path?} writes the file. Without path, it writes the file of the presentation. If the presentation has no file, the tool fails with no_file. With path, it replaces the file if it exists, and the path becomes the file of the presentation. It adds .sldr when 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 .sldr file. No editor is necessary.
  • The tool adds .pptx when path has 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 auto videos and shaders of a slide start when the slide shows.
  • A click, →, Space or Page Down starts the next on_click fill of the slide. The fills start in layer order, from the bottom. When all on_click fills 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.rotation is 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_frame two 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, width and height are 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_elements and find_elements also give bounds: 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

CodeCause
no_instanceNo editor is open.
several_instancesMore than one editor is open and the call gives no instance.
unknown_instanceNo editor has the given process id.
invalid_argumentsThe arguments do not match the schema of the tool.
unknown_slide, unknown_elementThe id does not exist.
render_failedThe editor cannot render the slide or the shader video.
operation_failedAn 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_revisionThe document changed after base_revision.
no_presentationThe window shows the Home screen. Call new_presentation or open_presentation.
unsaved_changesThe open presentation has unsaved changes and the call gives no discard: true.
no_filesave_presentation gets no path and the presentation has no file.
cannot_openopen_presentation cannot read the file.
ioThe editor cannot write the file.
connection_failedThe editor closed the connection.
closedThe editor closed during the call.
unknown_toolNo tool has the given name.
open_failedopen_editor cannot start an editor, or the editor does not accept calls in 20 seconds.
export_failedexport_pptx cannot make the PowerPoint file.
protocol, invalid_requestThe 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.

SliderinoDrawingML
1 slide unit7620 EMU
1 slide unit of font size0.6 point
Rotation in degrees, clockwiserot in 60 000ths of a degree
Opacity 0 to 1alpha in 100 000ths

Elements

ElementDeck
Rectanglep:sp with rect, or roundRect when it has a corner radius
Ellipsep:sp with ellipse
Linep:cxnSp with line, on the horizontal center axis of the frame
Textp:sp text box (txBox)
Groupp:grpSp with the rotation of the group
Tablep:graphicFrame with a:tbl; a group of shapes when it is turned
Video or shader fillp: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, diamond and oval. The sizes small, medium and large are sm, med and lg. They are 2, 3 and 5 stroke widths, as in Sliderino.
  • A linear gradient uses a:lin with 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 one a: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, hMerge and vMerge.
  • 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:

FeatureDifference
Radial gradient that is not a circle on the slideThe deck uses a circle with the mean of the two radii.
Radial gradient larger than the half diagonal of its boxThe colors past the half diagonal show as the color at the half diagonal.
Thick horizontal table borderSliderino extends it by half the stroke width at each end. PowerPoint does not.
Contained video in an ellipse or a rounded rectangleThe video shows in a rectangle.
Transparent video or shaderThe video plays opaque.
Turned tableA 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.

  1. 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.
  2. 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.
  3. PowerPoint reads uncompressed EOT fonts in .fntdata parts, and finds a face by the family and style names in the file.
  4. PowerPoint turns an image fill with its shape (rotWithShape="1"). LibreOffice does not.
  5. PowerPoint applies the transparency of a table border. LibreOffice does not.
  6. 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 pptx exports the debug scenes, reads each deck again (inspect.rs) and compares each shape with its element (parity.rs).
  • cargo test -- --ignored pptx renders 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, pdftoppm and dotnet.

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 resource skill://sliderino/SKILL.md, and sliderino skill writes it to stdout. To install it for Claude Code, run sliderino skill > ~/.claude/skills/sliderino/SKILL.md.

Fixed

  • The tool descriptions that MCP clients and sliderino tools show now match what the tools do. For example, get_basic_info lists the videos, list_fonts tells when the fonts are still loading, and save_presentation and export_pptx tell that they add the file extension.

[0.2.0] - 2026-09-28

Added

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_slide operation. get_selection gives the slides that are selected in the Slides tab in slides.

  • The Home screen. sliderino opens on it. Start a new presentation (Ctrl+N) or open a file (Ctrl+O) there.

  • Save presentations as .sldr files, 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 with sliderino 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_presentation and new_presentation (sliderino save, sliderino open-file and sliderino new). get_basic_info gives 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, iChannel0 and more). Edit the source in the Design tab; the problems of the source show with their line. Give the shader an image as iChannel0, 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 with video and shader, show shaders at a time with the time of get_screenshot, and make the MP4 of a shader with render_shader_video (sliderino shader-video). get_diagnostics reports 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_font and path or data, and remove a font family with remove_font and family.

  • 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 render makes a PNG image of a slide from a scene file.

  • sliderino-debug scene opens 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 the sliderino CLI (info, screenshot, apply, undo, call). Agent changes appear immediately and go into the same undo history as your changes. See docs/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_element and set_layer operations.

  • 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 rotation of set_frame. The angle is the final angle, so the same operation gives the same result each time. get_elements and find_elements give the bounds of 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_style and set_line_points. See “Shapes” and “Images” in docs/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_pptx tool, and the CLI with sliderino export-pptx -o deck.pptx. Give a .sldr file 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_editor opens an editor with a new presentation, as before. The other tools fail with no_presentation when the window shows the Home screen.
  • The agent tool get_selection gives the selected elements as a list.
  • Text opacity is now the opacity of the element. Agents set it with the opacity of set_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
OptionDefaultFunction
--ops FILE(required)The scene file.
-o FILE(required)The PNG file to write.
--slide ID1The slide to render.
--scale N1.0Pixels for each slide unit. The slide is 1600 × 900 units.
--overlayoffShow 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
OptionDefaultFunction
--ops FILE(required)The scene file.
-o FILEnoneCapture the slide to this PNG file, then close the editor. Without this option, the editor stays open.
--slide IDfirst slideThe slide to show.
--select IDnoneSelect this element. The capture shows its outline, handles and size.
--fulloffCapture 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}}
]
opFields
add_slideslide ({"id": 2}, optional), index (optional)
remove_slideid
move_slideid, index
add_elementslide, parent (optional; a group of the slide), element, index (optional; the element goes on top)
remove_elementid
move_elementid, parent (optional; without it, the slide), index (optional; the element goes on top)
groupid (optional), children (a list of element ids on one slide)
ungroupid
set_layerid, patch (name, hidden, locked, opacity; only the fields to change)
set_frameid, frame
set_text_sizingid, sizing
set_text_styleid, patch (only the fields to change)
set_shape_styleid, patch (fill, stroke, corner_radius, start, end; only the fields to change)
set_line_pointsid, from ({"x": 0, "y": 0}), to
replace_textid, range ({"start": 0, "end": 5}, in bytes), text
add_fontface, or path (from the folder of the scene) or data (base64) of a font file
remove_fontface, or family
add_imageid (optional), path (from the folder of the scene) or data (base64)
remove_imageid
add_videoid (optional), path (from the folder of the scene) or data (base64). Other formats than MP4 with H.264 change to it.
remove_videoid
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_sizingSee “Tables” in agent-api.md
batchops (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_font gives 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_font with path adds the faces of a TTF, OTF or TTC file. See debug/scenes/custom-font.json and “Fonts” in agent-api.md.
  • sizing is auto_width, auto_height or fixed.
  • An element has a text, a group, a rectangle, an ellipse, a line or a table. See debug/scenes/tables.json and “Tables” in agent-api.md. A group has children, 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, locked and opacity (0 to 1). Set them with set_layer. "name": null removes the name.
  • In style and patch, 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 the opacity of 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.

CommandFunction
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 pptx exports the debug scenes and compares each deck with its presentation. It needs no other programs.
  • cargo test -- --ignored pptx also 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

  1. 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.

  2. Write one secret in the repository, at Settings, Secrets and variables, Actions:

    SecretWhere it comes from
    CARGO_REGISTRY_TOKENcrates.io, at Account Settings, API Tokens, with the scope publish-new for the first release and publish-update after it

    GITHUB_TOKEN needs no work, because GitHub gives it to each workflow.

The steps

  1. 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-14
    

    Then 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.

  2. Write the same version in Cargo.toml. One command does it, and writes Cargo.lock:

    cargo install cargo-edit    # once
    cargo set-version 0.2.0
    
  3. Run what each change runs:

    cargo fmt --check
    cargo clippy --all-targets -- -D warnings
    cargo test
    
  4. Read what goes to crates.io, without sending it:

    cargo publish --dry-run --locked
    
  5. Commit, tag and push. The tag is the version with a v in front of it:

    git commit -am "release: 0.2.0"
    git tag v0.2.0
    git push && git push --tags
    

    Push the commit first and let the CI pass. publish-crates.yml does 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

WorkflowWhat it does
release.ymlPlans the release, builds each platform on its own runner, and makes the GitHub release with the archives, the checksums and the installer.
publish-crates.ymlWaits 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.