Powerpointer: Write Presentations in Markdown, Export to PowerPoint

- 4 minute read

Have you ever spent more time fighting PowerPoint formatting than actually writing your slides? md_to_pptx.py — which I’m calling Powerpointer — flips that around: you write your presentation as a plain Markdown file and get a polished .pptx out the other end.

Because every slide uses PowerPoint’s standard Title and Content placeholders, you can apply any theme afterwards and the fonts and colours update automatically.


How it works

The pipeline is straightforward:

flowchart LR A([Markdown file]) --> B[mistune parser] B --> C{Element type} C -->|Headings / bullets| D[Paragraph / Run objects] C -->|Pipe table| E[Table shape] C -->|Image / URL| F[Picture shape] C -->|Mermaid fence| G[mermaid.ink API] C -->|Code fence| H[Pygments + Pillow] C -->|LaTeX math| I[matplotlib mathtext] G --> F H --> F I --> F D --> J[(python-pptx)] E --> J F --> J J --> K([presentation.pptx])
  1. mistune parses the Markdown into an abstract syntax tree.
  2. A custom AST walker converts every node into one of three internal types: Paragraph, Table, or Picture.
  3. python-pptx writes each slide using those objects.
  4. Diagrams and code blocks are rasterised to PNG first so they embed cleanly.

Installation

git clone https://github.com/your-username/Powerpointer.git
cd Powerpointer

python -m venv .venv
.venv\Scripts\activate        # Windows
# source .venv/bin/activate   # macOS / Linux

pip install -r requirements.txt

Dependencies:

PackageRole
python-pptxCreates and writes .pptx files
mistuneParses Markdown to an AST
PillowScales images and renders code blocks
requestsFetches remote images and Mermaid PNGs
pygmentsSyntax-highlights code fences
matplotlibRenders LaTeX math ($$…$$ blocks)

Basic usage

# Minimal
python md_to_pptx.py slides.md presentation.pptx

# Apply a corporate theme
python md_to_pptx.py slides.md presentation.pptx --template brand.pptx

# Break slides on every # heading instead of ---
python md_to_pptx.py slides.md presentation.pptx --split h1

# Behind a proxy that performs SSL inspection
python md_to_pptx.py slides.md presentation.pptx --insecure

Slide-splitting modes

flowchart TD
    rule["--split rule (default)\nnew slide on ---"]
    h1["--split h1\nnew slide on --- or # heading"]
    h2["--split h2\nnew slide on ---, # heading, or ## heading"]
    rule --> h1 --> h2

Writing slides in Markdown

Title slide

# My Presentation
> Subtitle or author name

The first slide automatically uses the Title Slide layout. A blockquote immediately below the title becomes the subtitle.

Bullets and inline formatting

## Agenda

- Top-level bullet
  - Nested bullet (indent with 2 spaces)
    - Deeply nested
- **Bold**, *italic*, `monospace`

Nesting is unlimited; each extra indent level increases the PowerPoint bullet level.

Tables

Standard GFM pipe tables render with a styled header row and alternating row colours:

## Tool comparison

| Tool        | Input      | Output |
|-------------|------------|--------|
| Powerpointer | Markdown  | .pptx  |
| Marp        | Markdown   | HTML / PDF |
| reveal.js   | HTML / MD  | Browser slides |

Mermaid diagrams

Wrap any Mermaid code in a fenced block tagged mermaid. The script calls mermaid.ink at 2 000 px wide and embeds the PNG:

```mermaid
sequenceDiagram
    User->>Script: python md_to_pptx.py slides.md out.pptx
    Script->>mermaid.ink: POST diagram code
    mermaid.ink-->>Script: PNG bytes
    Script->>out.pptx: embed Picture shape
```

Which, rendered here, looks like:

sequenceDiagram
    User->>Script: python md_to_pptx.py slides.md out.pptx
    Script->>mermaid.ink: POST diagram code
    mermaid.ink-->>Script: PNG bytes
    Script->>out.pptx: embed Picture shape

Syntax-highlighted code blocks

Any fenced code block with a language tag is rendered to a PNG with a Monokai dark theme using Pygments + Pillow and embedded as a picture shape — so it looks exactly like a code editor screenshot, without any manual formatting:

```python
def greet(name: str) -> str:
    return f"Hello, {name}!"
```

LaTeX math

Display-math blocks delimited by $$ are rendered to PNG via matplotlib mathtext:

$$
E = mc^2
$$

No internet connection required for math — it all happens locally.

Images

![A local chart](./charts/revenue.png)
![Remote image](https://example.com/photo.jpg)

A slide containing only a single image fills the entire content area. When a slide has both text and visuals, the content area is split ~40 / 60 vertically.


Under the hood: the Mermaid encoding

One interesting implementation detail is how diagrams are sent to mermaid.ink. The service accepts diagram code as a URL segment, so it must be encoded. The modern pako: format compresses the JSON payload with zlib (matching JavaScript’s pako.deflate()) for shorter URLs and better Unicode support:

def _encode_mermaid_pako(code: str) -> str:
    payload = json.dumps({"code": code, "mermaid": {"theme": "default"}})
    # zlib.compress uses wbits=15 — a zlib stream with header + Adler-32,
    # which matches pako.deflate() in JS (NOT raw DEFLATE with wbits=-15).
    compressed = zlib.compress(payload.encode("utf-8"))
    return base64.urlsafe_b64encode(compressed).decode().rstrip("=")


def render_mermaid(code: str) -> Optional[bytes]:
    pako_encoded = _encode_mermaid_pako(code)
    url = f"https://mermaid.ink/img/pako:{pako_encoded}?type=png&width=2000"
    data = _fetch_with_ssl_retry(url)
    if data:
        return data
    # Fallback: plain base64 of raw mermaid code (legacy format)
    encoded = base64.urlsafe_b64encode(code.encode()).decode()
    url = f"https://mermaid.ink/img/{encoded}?type=png&width=2000"
    return _fetch_with_ssl_retry(url)

The script also auto-retries without TLS verification on SSL errors, which is useful behind corporate proxies — or you can pass --insecure explicitly.


Applying a theme

After generating the file, open it in PowerPoint:

  1. Design → Themes — pick any built-in theme.
  2. Because every text block sits in a standard placeholder, the theme’s fonts and accent colours apply in one click.
  3. To start from your own master slide instead, pass it via --template your_theme.pptx.

Project structure

Powerpointer/
├── md_to_pptx.py       # Main converter (~700 lines)
├── requirements.txt
├── example.md          # Demo presentation
└── example_output.pptx # Pre-generated sample output

The full source is on GitHub. Drop a ⭐ if it saves you an hour of slide-wrangling.

Leave a Comment