| name | powerpoint |
| description | PowerPoint slide deck generation and management using python-pptx with YAML-driven content and styling |
| license | MIT |
| compatibility | Requires uv, Python 3.11+, PowerShell 7+, and LibreOffice |
| metadata | {"authors":"microsoft/hve-core","spec_version":"1.0","last_updated":"2026-08-09"} |
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:
curl -LsSf https://astral.sh/uv/install.sh | sh
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
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.
brew install --cask libreoffice
brew install poppler
sudo apt-get install libreoffice poppler-utils
winget install TheDocumentFoundation.LibreOffice
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:
npm install -g @github/copilot-cli
copilot auth login
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:
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 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 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 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.
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: 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, 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 and OSV 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:
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.