# Powerpoint

> PowerPoint slide deck generation and management using python-pptx with YAML-driven content and styling

- Skill: `gabrielmoreira/powerpoint-3` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add gabrielmoreira/powerpoint-3`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gabrielmoreira/powerpoint-3/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT
- Author: gabrielmoreira (https://skillmd.com/u/gabrielmoreira)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/gabrielmoreira/powerpoint-3

---


# PowerPoint Skill

Generates, updates, and manages PowerPoint slide decks using `python-pptx` with YAML-driven content and styling definitions.

## Overview

This skill provides Python scripts that consume YAML configuration files to produce PowerPoint slide decks. Each slide is defined by a `content.yaml` file describing its layout, text, and shapes. A `style.yaml` file defines dimensions, template configuration, layout mappings, metadata, and defaults.

SKILL.md covers technical reference: prerequisites, commands, script architecture, API constraints, and troubleshooting. For conventions and design rules (element positioning, visual quality, color and contrast, contextual styling), follow `pptx.instructions.md`.

## Prerequisites

### PowerShell

The `Invoke-PptxPipeline.ps1` script handles virtual environment creation and dependency installation automatically via `uv sync`. Requires `uv`, Python 3.11+, and PowerShell 7+.

### Installing uv

If `uv` is not installed:

```bash
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

# Via pip (fallback)
pip install uv
```

### System Dependencies (Export and Validation)

The Export and Validate actions require LibreOffice for PPTX-to-PDF conversion and optionally `pdftoppm` from poppler for PDF-to-JPG rendering. When `pdftoppm` is not available, PyMuPDF handles the image rendering.

The Validate action's vision-based checks require the GitHub Copilot CLI for model access.

```bash
# macOS
brew install --cask libreoffice
brew install poppler        # optional, provides pdftoppm

# Linux
sudo apt-get install libreoffice poppler-utils

# Windows (winget preferred, choco fallback)
winget install TheDocumentFoundation.LibreOffice
# choco install libreoffice-still      # alternative
# poppler: no winget package; use choco install poppler (optional, provides pdftoppm)
```

### Copilot CLI (Vision Validation)

The `validate_slides.py` script uses the GitHub Copilot SDK to send slide images to vision-capable models. The Copilot CLI must be installed and authenticated:

```bash
# Install Copilot CLI
npm install -g @github/copilot-cli

# Authenticate (uses the same GitHub account as VS Code Copilot)
copilot auth login

# Verify
copilot --version
```

### Required Files

* `style.yaml` — Dimensions, defaults, template configuration, and metadata
* `content.yaml` — Per-slide content definition (text, shapes, images, layout)
* (Optional) `content-extra.py` — Custom Python for complex slide drawings

## Content Directory Structure

All slide content lives under the working directory's `content/` folder:

```text
content/
├── global/
│   ├── style.yaml              # Dimensions, defaults, template config, and theme metadata
│   └── voice-guide.md          # Voice and tone guidelines
├── slide-001/
│   ├── content.yaml            # Slide 1 content and layout
│   └── images/                 # Slide-specific images
│       ├── background.png
│       └── background.yaml     # Image metadata sidecar
├── slide-002/
│   ├── content.yaml            # Slide 2 content and layout
│   ├── content-extra.py        # Custom Python for complex drawings
│   └── images/
│       └── screenshot.png
├── slide-003/
│   ├── content.yaml
│   └── images/
│       ├── diagram.png
│       └── diagram.yaml
└── ...
```

## Global Style Definition (`style.yaml`)

The global `style.yaml` defines dimensions, template configuration, layout mappings, metadata, and defaults. Color and font choices are specified per-element in each slide's `content.yaml` rather than centralized in the style file.

See the [style.yaml template](style-yaml-template.md) for the full template, field reference, and usage instructions.

## Per-Slide Content Definition (`content.yaml`)

Each slide's `content.yaml` defines layout, text, shapes, and positioning. All position and size values are in inches. Color values use `#RRGGBB` hex format or `@theme_name` references.

Text contract: markdown-like list lines in `textbox.text` and `shape.text` are interpreted as PowerPoint lists during rendering. Unordered markers (`-`, `+`, `*`) become bulleted paragraphs, ordered markers (`1.`, `1)`) become auto-numbered paragraphs, and leading indentation maps to paragraph level.

See the [content.yaml template](content-yaml-template.md) for the full template, supported element types, supported shape types, and usage instructions.

## Complex Drawings (`content-extra.py`)

When a slide requires complex drawings that cannot be expressed through `content.yaml` element definitions, create a `content-extra.py` file in the slide folder. The `render()` function signature is fixed. The build script calls it after placing standard `content.yaml` elements.

See the [content-extra.py template](content-extra-py-template.md) for the full template, function parameters, and usage guidelines.

### Security Validation

`content-extra.py` execution is disabled by default. When a slide folder contains one and `--allow-scripts` is not passed, the build fails with an error naming the file. Pass `--allow-scripts` to authorize execution after reviewing the script.

When execution is authorized, the build script performs AST-based static analysis before running the file and rejects the patterns below. This analysis is a lint that catches obvious mistakes early. It is not a security boundary: a blocked module reached through an alias is not detected, and `pathlib` and `open` are permitted. Authorization is the control.

**Allowed imports:**

* `pptx` and all `pptx.*` submodules
* Safe standard-library modules (e.g., `math`, `copy`, `json`, `re`, `pathlib`, `collections`, `itertools`, `functools`, `typing`, `enum`, `dataclasses`, `decimal`, `fractions`, `string`, `textwrap`)

**Blocked imports:**

* `subprocess`, `os`, `shutil`, `socket`, `ctypes`, `signal`, `multiprocessing`, `threading`, `http`, `urllib`, `ftplib`, `smtplib`, `imaplib`, `poplib`, `xmlrpc`, `webbrowser`, `code`, `codeop`, `compileall`, `py_compile`, `zipimport`, `pkgutil`, `runpy`, `ensurepip`, `venv`, `sqlite3`, `tempfile`, `shelve`, `dbm`, `pickle`, `marshal`, `importlib`, `sys`, `telnetlib`
* Any third-party package not on the allowlist

**Blocked builtins:**

* Dangerous: `eval`, `exec`, `__import__`, `compile`, `breakpoint`
* Indirect bypass: `getattr`, `setattr`, `delattr`, `globals`, `locals`, `vars`
* Attribute-form calls onto `builtins`, `os`, `sys`, `subprocess`, and `importlib` (for example `builtins.eval(...)`)

**`--allow-scripts` flag:**

Pass `--allow-scripts` to authorize execution of `content-extra.py` files. Without it, a present script fails the build rather than being skipped, so a deck never silently loses custom drawings. The flag authorizes execution; it does not skip the lint.

This is a behavior change. A deck that previously built with a `content-extra.py` present now requires `--allow-scripts`, and a script relying on a blocked import no longer has a bypass.

```bash
python scripts/build_deck.py \
  --content-dir content/ \
  --style content/global/style.yaml \
  --output slide-deck/presentation.pptx \
  --allow-scripts
```

When validation fails, the build raises `ContentExtraError` with a message identifying the violation and file path.

## Script Reference

The full command surface lives in [references/script-reference.md](references/script-reference.md): build a deck, build from a template, update specific slides, extract content from an existing PPTX, validate, export slides to images or SVG, dry-run validation, generate theme variants, and embed audio.

## Script Architecture

The build and extraction scripts use shared modules in the `scripts/` directory:

| Module                 | Purpose                                                                                                                                                                                                |
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `pptx_utils.py`        | Shared utilities: exit codes, logging configuration, slide filter parsing, unit conversion (`emu_to_inches()`), YAML loading                                                                           |
| `pptx_colors.py`       | Color resolution (`#hex`, `@theme`, dict with brightness), theme color map (16 entries)                                                                                                                |
| `pptx_fonts.py`        | Font resolution, family normalization, weight suffix handling, alignment mapping                                                                                                                       |
| `pptx_shapes.py`       | Shape constant map (29 entries + circle alias), auto-shape name mapping, rotation utilities                                                                                                            |
| `pptx_fills.py`        | Solid, gradient, and pattern fill application/extraction; line/border styling with dash styles                                                                                                         |
| `pptx_text.py`         | Text frame properties (margins, auto-size, vertical anchor), paragraph properties (spacing, level), run properties (underline, hyperlink), markdown-like list parsing to bullet/auto-number paragraphs |
| `pptx_tables.py`       | Table element creation and extraction with cell merging, banding, and per-cell styling                                                                                                                 |
| `pptx_charts.py`       | Chart element creation and extraction for 12 chart types (column, bar, line, pie, scatter, bubble, etc.)                                                                                               |
| `validate_deck.py`     | PPTX-only validation for speaker notes and slide count                                                                                                                                                 |
| `validate_geometry.py` | Structural validation for element edge margins, adjacent gaps, boundary overflow, and title clearance                                                                                                  |
| `validate_slides.py`   | Vision-based slide issue detection and quality validation via Copilot SDK with built-in checks and plain-text per-slide output                                                                         |
| `render_pdf_images.py` | PDF-to-JPG rendering via PyMuPDF with optional slide-number-based naming                                                                                                                               |
| `generate_themes.py`   | Theme variant generation from a base content directory using a color mapping YAML file                                                                                                                 |
| `embed_audio.py`       | WAV audio embedding into PPTX slides with per-slide file matching and off-screen audio icon placement                                                                                                  |
| `export_svg.py`        | PPTX-to-SVG export via LibreOffice PDF conversion and PyMuPDF SVG rendering                                                                                                                            |

## python-pptx Constraints

* python-pptx does NOT support SVG images. Always convert to PNG via `cairosvg` or `Pillow`.
* python-pptx cannot create new slide masters or layouts programmatically. Use blank layouts or start from a template PPTX with the `--template` argument.
* Transitions and animations are preserved when opening and saving existing files, but cannot be created or modified via the API.
* When extracting content, slide master and layout inheritance means many text elements have no inline styling. Add explicit font properties in content YAML before rebuilding.
* The Export and Validate actions require LibreOffice for PPTX-to-PDF conversion. The PowerShell orchestrator checks for LibreOffice availability before starting and provides platform-specific install instructions if missing.
* Accessing `background.fill` on slides with inherited backgrounds replaces them with `NoFill`. Check `slide.follow_master_background` before accessing the fill property.
* Gradient fills use the python-pptx `GradientFill` API with `GradientStop` objects. Each stop specifies a position (0–100) and a color.
* Theme colors resolve via `MSO_THEME_COLOR` enum. Brightness adjustments apply through the color format's `brightness` property.
* Template-based builds load layouts by name or index. Layout name resolution falls back to index 6 (blank) when no match is found.

## Security Considerations

This skill processes PDF files via [PyMuPDF](https://pymupdf.readthedocs.io/), which wraps the MuPDF C library. MuPDF parses untrusted binary structures (cross-reference tables, stream objects, font definitions) and historical CVEs have shown that memory-safety bugs in C parsers can lead to crashes, memory disclosure, or in rare cases code execution.

### Mitigations in place

* `scripts/pdf_safety.py` validates every PDF (existence, regular-file, size <= 100 MB, `%PDF-` magic bytes, page count <= 1000) before calling `fitz.open()`.
* All `fitz` operations are wrapped in `safe_open_pdf()`, which converts MuPDF exceptions into typed `PdfSafetyError` subclasses (`PdfTooLargeError`, `PdfInvalidFormatError`, `PdfTooManyPagesError`, `PdfParseError`, `PdfRenderError`).
* `pymupdf` is version-pinned in `pyproject.toml` (`>=1.27.1,<2.0`) to track security fixes without silently adopting a major-version API change.

> **Defense-in-depth note**: the 5-byte `%PDF-` magic check is a necessary but not sufficient guarantee of structural validity. A crafted small file that begins with `%PDF-` still reaches the MuPDF parser, where memory-safety bugs may exist. Callers MUST keep PyMuPDF patched against the latest advisories (tracked via microsoft/hve-core#1020 / `pip-audit`) and continue to treat such inputs as untrusted. The `PdfSafetyError` hierarchy (`PdfTooLargeError`, `PdfInvalidFormatError`, `PdfTooManyPagesError`, `PdfParseError`, `PdfRenderError`) is one defense layer alongside the version pin and CVE monitoring; no single layer is sufficient on its own.

### Accepted risk

These mitigations reduce but do not eliminate the C-extension attack surface. Consumers SHOULD treat PDF inputs from outside this skill's PPTX-to-PDF pipeline as untrusted and apply additional sandboxing (subprocess, container) before feeding adversarial input.

### Update policy

Re-check [NVD](https://nvd.nist.gov) and [OSV](https://osv.dev) advisories for MuPDF and PyMuPDF quarterly and on every pip-audit alert.

### Cross-references

* microsoft/hve-core#1018 — original hardening request
* microsoft/hve-core#1020 — pip-audit CI for ongoing CVE monitoring (separate effort)

## Troubleshooting

| Issue                                  | Cause                                              | Solution                                                                                         |
|----------------------------------------|----------------------------------------------------|--------------------------------------------------------------------------------------------------|
| SVG runtime error                      | python-pptx cannot embed SVG                       | Convert to PNG via `cairosvg` before adding                                                      |
| Text overlay between elements          | Insufficient vertical spacing                      | Follow element positioning conventions in `pptx.instructions.md`                                 |
| Width overflow off-slide               | Element extends beyond slide boundary              | Follow element positioning conventions in `pptx.instructions.md`                                 |
| Bright accent color unreadable as fill | White text on bright background                    | Darken accent to ~60% saturation for box fills                                                   |
| Background fill replaced with NoFill   | Accessed `background.fill` on inherited background | Check `slide.follow_master_background` before accessing                                          |
| Missing speaker notes                  | Notes not specified in `content.yaml`              | Add `speaker_notes` field to every content slide                                                 |
| LibreOffice not found during Validate  | Validate exports slides to images first            | Install LibreOffice: `brew install --cask libreoffice` (macOS)                                   |
| `uv` not found                         | uv package manager not installed                   | Install uv: `curl -LsSf https://astral.sh/uv/install.sh \| sh` (macOS/Linux) or `pip install uv` |
| Python not found by uv                 | No Python 3.11+ on PATH                            | Install via `uv python install 3.11` or `pyenv install 3.11`                                     |
| `uv sync` fails                        | Missing or corrupt `.venv`                         | Delete `.venv/` at the skill root and re-run `uv sync`                                           |
| Import errors in scripts               | Dependencies not installed or stale venv           | Run `uv sync` from the skill root to recreate the environment                                    |

## Environment Recovery

When scripts fail due to missing modules, import errors, or a corrupt virtual environment, recover with:

```bash
cd .github/skills/experimental/powerpoint
rm -rf .venv
uv sync
```

This recreates the virtual environment from scratch using `pyproject.toml` as the single source of truth. The `Invoke-PptxPipeline.ps1` orchestrator runs `uv sync` automatically on each invocation unless `-SkipVenvSetup` is passed.

When `uv` itself is not available, install it first (see Installing uv above), then retry. When Python 3.11+ is not available, run `uv python install 3.11` to have uv fetch and manage the interpreter.


