Skip to content

tomastimelock/slide-render

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

About

Render a deck-spec Deck to PPTX, PDF, and HTML � three renderers, one API.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors