Render a deck-spec Deck to PPTX, PDF, and HTML — three renderers, one API.
Project description
slide-render
Render a deck-spec Deck to PPTX, PDF, and HTML — three renderers, one API.
Built at Trollfabriken AITrix AB for the AIMOS Insight municipal-audit explainer pipeline. One Deck object compiles to a PowerPoint handout for kommun officials, a PDF for the public record, and an HTML embed for the news site — same content, three formats, single source of truth. PPTX uses python-pptx, PDF uses Playwright Chromium, HTML is the lingua franca underneath.
What it solves
| Previous problem | Solution |
|---|---|
python-pptx requires 60+ lines per slide for consulting-quality output |
Eleven prebuilt layouts; declarative Slide(layout="content", title=..., body=...) |
| Marp/Slidev give HTML/PDF but not editable PPTX | Three renderers, one Deck source of truth |
| Aspose/Spire are paid commercial with a .NET runtime | MIT, pure Python, no .NET, no licence cost |
| LibreOffice headless PDF export is slow and fragile across versions | Playwright Chromium prints from our HTML; deterministic |
| Adding a custom theme means editing every renderer | One theme directory with CSS + optional PPTX template; all three renderers pick it up |
| Swedish characters break in naive PPTX writers | UTF-8 throughout; tested with Swedish fixtures |
| Slide layouts diverge between PPTX and PDF outputs | PDF is the HTML rendering printed by Chromium; same visual everywhere except PPTX |
| Markdown-in-body needs preprocessing | Markdown spans (**bold**, *italic*, `code`) handled directly in renderers |
Installation
pip install slide-render
playwright install chromium # for PDF export
Optional extras:
pip install slide-render[images] # Pillow for image manipulation
pip install slide-render[fonts] # fonttools for custom font handling
pip install slide-render[dev] # pytest, ruff, pypdf, build
Runtime requirements:
- Python >= 3.10
- Playwright Chromium browser (PDF only; PPTX and HTML do not need it)
Quick start
from deck_spec import Deck, Slide
from slide_render import render
deck = Deck(
title="Q1 Audit Findings",
slides=[
Slide(layout="title", title="Q1 Audit Findings",
subtitle="Uddevalla kommun · 2026"),
Slide(layout="stat", title="Beslut utan beslutsunderlag",
stat_value="34%", stat_label="av granskade ärenden",
stat_supporting="Mot mål 0%"),
Slide(layout="bullet", title="Rekommendationer",
bullets=[
"Rättsutredning före varje beslut",
"Digital signering av beslutsunderlag",
"Stickprovskontroll månadsvis",
]),
Slide(layout="comparison", title="Före vs efter",
columns=[
{"heading": "Före", "bullets": ["Manuell registrering",
"Spridda filer"]},
{"heading": "Efter", "bullets": ["Automatisk loggning",
"Central databas"]},
]),
],
)
# Three outputs, one source
render(deck, "audit.pptx")
render(deck, "audit.pdf")
render(deck, "audit_html/", format="html")
How it works
Format is auto-detected from the output path suffix. Pass format= to override.
Deck ──► slide-render ──► PPTX (python-pptx; native shapes and master layouts)
├──► HTML (Jinja2 templates; one section per slide)
└──► PDF (HTML rendered above, printed by Playwright Chromium)
PDF shares the HTML rendering path. One layout codebase covers both. Adding a new layout requires one HTML template; PPTX gets its own dedicated builder because python-pptx's placeholder model is incompatible with CSS layout.
Lower-level entry points:
from slide_render.pptx import render_pptx
from slide_render.pdf import render_pdf
from slide_render.html import render_html, render_html_string
render_pptx(deck, "out.pptx", config=...)
render_pdf(deck, "out.pdf", config=...)
render_html(deck, "out_dir/", config=...) # writes index.html + assets/
html_str = render_html_string(deck, config=...) # single-string variant
Themes
Four themes ship with the package. Each is a directory under slide_render/themes/.
| Theme | Description |
|---|---|
default |
Clean modern style. Blue accent, white background. |
civic |
Serious tone for municipal and legal content. Navy accent, generous margins. |
audit |
Report-style. Accent on numbers. Suited to AIMOS Insight scorecards. |
explainer |
Educational, friendly, larger type. Used for parent-facing guides. |
One Deck renders cleanly in any of the four themes. Theme is a runtime choice:
from slide_render import render, RenderConfig, list_themes
print(list_themes()) # ["default", "civic", "audit", "explainer"]
render(deck, "audit.pptx", config=RenderConfig(theme_name="civic"))
Configuration
RenderConfig is a Pydantic v2 model. All fields are optional.
from slide_render import RenderConfig
config = RenderConfig(
# Universal
theme_name="civic", # override the theme named in the Deck
fonts_dir=Path("./fonts"), # extra font directory to make available
# PPTX
pptx_template=Path("base.pptx"), # clone from an existing .pptx template
pptx_master_layout=True, # use master slide layouts vs raw shapes
# PDF
pdf_page_size="16:9", # "16:9" | "4:3" | "A4" | "letter"
pdf_print_background=True,
pdf_orientation="landscape", # "landscape" | "portrait"
pdf_chromium_executable=None, # override Playwright's Chromium path
# HTML
html_standalone=True, # inline all CSS; no external files
html_reveal_compat=False, # emit Reveal.js-compatible <section> structure
html_include_speaker_notes=False, # add <aside class="notes">
# Shared
verbose=False,
)
| Field | Default | Effect |
|---|---|---|
theme_name |
None |
Overrides deck.theme.name |
pdf_page_size |
"16:9" |
Sets Chromium paper size |
html_standalone |
True |
Produces a single self-contained file |
html_reveal_compat |
False |
Wraps slides in Reveal.js <section> tags |
pptx_template |
None |
Starts from an existing .pptx instead of blank |
verbose |
False |
Prints per-slide progress to stdout |
CLI
Auto-detect format from extension:
slide-render deck.json --output audit.pptx
slide-render deck.json --output audit.pdf
slide-render deck.json --output audit_html/
Multiple outputs in one pass:
slide-render deck.json --output audit.pptx --output audit.pdf --output audit_html/
Theme override:
slide-render deck.json --theme civic --output audit.pptx
List available themes:
slide-render themes
Validate before rendering:
slide-render deck.json --validate --output audit.pdf
Package structure
src/slide_render/
├── __init__.py ← render() dispatcher and public exports
├── config.py ← RenderConfig (Pydantic v2)
├── cli.py ← CLI entry point
├── dispatch.py ← auto-detect format from output path
├── exceptions.py ← SlideRenderError and subclasses
├── themes/
│ ├── __init__.py
│ ├── registry.py ← discover and load themes
│ ├── default/ ← theme.json, styles.css, pptx_template.pptx
│ ├── civic/
│ ├── audit/
│ └── explainer/
├── pptx/
│ ├── __init__.py
│ ├── renderer.py ← render_pptx orchestrator
│ ├── layouts.py ← map deck-spec layout → python-pptx layout
│ ├── elements.py ← text, image, shape, table emit functions
│ ├── theme_apply.py ← apply theme colours/fonts to slide master
│ └── notes.py ← write speaker notes to slides
├── html/
│ ├── __init__.py
│ ├── renderer.py ← render_html and render_html_string
│ ├── assets.py ← copy/inline CSS, images, fonts
│ ├── jinja_env.py ← Jinja2 environment with StrictUndefined
│ └── templates/
│ ├── deck.html.j2 ← outer HTML; one section per slide
│ ├── base.css.j2 ← base styling rules
│ ├── reveal_compat.html.j2
│ └── layouts/ ← one .html.j2 per layout name
│ ├── title.html.j2
│ ├── content.html.j2
│ ├── bullet.html.j2
│ ├── image.html.j2
│ ├── image_caption.html.j2
│ ├── two_column.html.j2
│ ├── comparison.html.j2
│ ├── stat.html.j2
│ ├── quote.html.j2
│ ├── section.html.j2
│ └── blank.html.j2
├── pdf/
│ ├── __init__.py
│ ├── renderer.py ← render_pdf: HTML render then Chromium print
│ └── chromium.py ← Playwright launch and page.pdf() call
└── shared/
├── __init__.py
├── element_helpers.py ← image loading, base64 decode, file:// URIs
├── color_utils.py ← palette key resolution ("accent1" → hex)
└── text_format.py ← markdown span handling (bold, italic, code)
© Trollfabriken AITrix AB — MIT licensed
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file slide_render-0.1.0.tar.gz.
File metadata
- Download URL: slide_render-0.1.0.tar.gz
- Upload date:
- Size: 26.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2194513a367fd1f959b9ea8b1000600a11b1139bdb4431bdfc777e65d78a4967
|
|
| MD5 |
654edef0fa81b7c574a2d4fe4c436062
|
|
| BLAKE2b-256 |
20a2179c02c55bf538010314da0ded6c2e7b214d27d90ce357ff33cc6f270a1c
|
Provenance
The following attestation bundles were made for slide_render-0.1.0.tar.gz:
Publisher:
release.yml on tomastimelock/slide-render
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
slide_render-0.1.0.tar.gz -
Subject digest:
2194513a367fd1f959b9ea8b1000600a11b1139bdb4431bdfc777e65d78a4967 - Sigstore transparency entry: 1602106263
- Sigstore integration time:
-
Permalink:
tomastimelock/slide-render@ea77528d1a9812b41ad0177ec7bf9f0f0e91c12f -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/tomastimelock
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ea77528d1a9812b41ad0177ec7bf9f0f0e91c12f -
Trigger Event:
push
-
Statement type:
File details
Details for the file slide_render-0.1.0-py3-none-any.whl.
File metadata
- Download URL: slide_render-0.1.0-py3-none-any.whl
- Upload date:
- Size: 44.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5fece896109b9b8d1c5757f7f0c70ebb6c8e0e86965e06164fe14ff73f697c94
|
|
| MD5 |
d0e546fd858ced1c7ff06673a8c52f5c
|
|
| BLAKE2b-256 |
58c3108133f049f979249054568972bfb09b86038387ba1200a66583ec55a678
|
Provenance
The following attestation bundles were made for slide_render-0.1.0-py3-none-any.whl:
Publisher:
release.yml on tomastimelock/slide-render
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
slide_render-0.1.0-py3-none-any.whl -
Subject digest:
5fece896109b9b8d1c5757f7f0c70ebb6c8e0e86965e06164fe14ff73f697c94 - Sigstore transparency entry: 1602106284
- Sigstore integration time:
-
Permalink:
tomastimelock/slide-render@ea77528d1a9812b41ad0177ec7bf9f0f0e91c12f -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/tomastimelock
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ea77528d1a9812b41ad0177ec7bf9f0f0e91c12f -
Trigger Event:
push
-
Statement type: