Metadata-Version: 2.4
Name: slide-render
Version: 0.1.0
Summary: Render a deck-spec Deck to PPTX, PDF, and HTML — three renderers, one API.
Project-URL: Homepage, https://github.com/tomastimelock/slide-render
Project-URL: Repository, https://github.com/tomastimelock/slide-render
Project-URL: Issues, https://github.com/tomastimelock/slide-render/issues
Author-email: Trollfabriken AITrix AB <dev@trollfabriken.se>
License-Expression: MIT
License-File: LICENSE
Keywords: deck-spec,html,llm,pdf,pptx,presentation,slides
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics :: Presentation
Classifier: Topic :: Office/Business
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup :: HTML
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: deck-spec>=0.1
Requires-Dist: jinja2>=3.1
Requires-Dist: playwright>=1.40
Requires-Dist: pydantic>=2.5
Requires-Dist: python-pptx>=1.0
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: pillow; extra == 'dev'
Requires-Dist: pypdf>=3.0; extra == 'dev'
Requires-Dist: pytest-cov>=4; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: fonts
Requires-Dist: fonttools>=4.40; extra == 'fonts'
Provides-Extra: images
Requires-Dist: pillow>=10.0; extra == 'images'
Description-Content-Type: text/markdown

# 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

```bash
pip install slide-render
playwright install chromium  # for PDF export
```

Optional extras:

```bash
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

```python
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:

```python
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:

```python
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.

```python
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:

```bash
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:

```bash
slide-render deck.json --output audit.pptx --output audit.pdf --output audit_html/
```

Theme override:

```bash
slide-render deck.json --theme civic --output audit.pptx
```

List available themes:

```bash
slide-render themes
```

Validate before rendering:

```bash
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
