Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c6285e3
feat(skills): add 6 PowerPoint skill enhancements
Apr 28, 2026
8d53657
fix(skills): resolve ruff lint and format violations
Apr 28, 2026
333416f
test(skills): add tests for 4 new PowerPoint skill scripts
Apr 28, 2026
705b9db
fix(skills): address PR review feedback from katriendg and CI
Apr 28, 2026
afea654
Merge branch 'main' into users/auyidi/pptx-skill-enhancements
auyidi1 Apr 28, 2026
11b1472
style(skills): fix ruff format on export_svg.py and test_generate_the…
Apr 28, 2026
dae25d3
fix(skills): address second round PR review feedback
Apr 28, 2026
5c40476
fix(skills): address third round PR review feedback
Apr 29, 2026
50ffd86
Merge branch 'main' into users/auyidi/pptx-skill-enhancements
auyidi1 Apr 29, 2026
a285e8d
Merge branch 'main' into users/auyidi/pptx-skill-enhancements
auyidi1 May 1, 2026
7f72e47
fix(skills): address fourth round PR review feedback
May 1, 2026
f103c6b
fix(skills): address fifth round PR review feedback
May 1, 2026
c825af3
fix(skills): address sixth round PR review feedback
May 1, 2026
a665785
fix(skills): address seventh round PR review feedback
May 1, 2026
94edc90
fix(skills): address eighth round PR review feedback
May 1, 2026
d85988d
fix(skills): add CmdletBinding and explicit Mandatory to PS1 wrappers
May 1, 2026
f47ee0e
fix(skills): address ninth round PR review feedback
May 1, 2026
faba1e7
fix(skills): address PR Review bot findings
May 1, 2026
c5ae6d3
fix(skills): correct step counts and extract run() in validate_geometry
May 1, 2026
6e54a0b
fix(skills): support single-quoted hex and fix theme docs
May 1, 2026
2daa49c
fix(skills): add Exception handler in validate_geometry and use Pasca…
May 2, 2026
530665e
fix(skills): add .pptx extension check and PS1 convention fixes
May 4, 2026
8e0cd01
Merge branch 'main' into users/auyidi/pptx-skill-enhancements
auyidi1 May 4, 2026
059796c
fix(skills): add future annotations to 4 Python scripts and document …
May 5, 2026
6c7dce5
fix(skills): close fitz.Document via context manager and make --outpu…
May 5, 2026
656de64
fix(skills): add PyMuPDFError class, fix docstring, document add_movi…
May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/instructions/experimental/pptx.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ For partial rebuild workflows (update a few slides in an existing deck):

## Validation Criteria

These criteria define the quality standards agents verify after building or updating slides. Visual checks use `validate_slides.py` and PPTX-only checks use `validate_deck.py`.
These criteria define the quality standards agents verify after building or updating slides. The Validate pipeline runs three checks in sequence: PPTX property validation (`validate_deck.py`), geometric validation (`validate_geometry.py`), and optionally vision-based validation (`validate_slides.py`).

Geometric validation runs automatically during the Validate action and checks the element positioning rules below programmatically. It catches margin violations, boundary overflow, and insufficient gaps without requiring vision model access.

### Element Positioning

Expand Down Expand Up @@ -116,6 +118,10 @@ These criteria define the quality standards agents verify after building or upda

Use `#RRGGBB` hex values or `@theme_name` references for all colors. See the Color Syntax section in `content-yaml-template.md` for the full specification including theme brightness adjustments and dict syntax.

### Theme Colors in content-extra.py

Comment thread
auyidi1 marked this conversation as resolved.
When `style.yaml` defines a `themes` section, the build script populates `style[\"colors\"]` with the color map for the theme assigned to each slide via `themes[].slides`. Slides not explicitly assigned fall back to the first theme in the list. Use `style.get(\"colors\", {}).get(\"accent_blue\", \"#0078D4\")` in `content-extra.py` to reference theme-aware colors. This enables theme portability. The same script produces correct colors across all theme variants without regex replacement.

## Contextual Styling

Slide decks often contain multiple visual themes (title slides, content slides, section dividers, dark vs. light themes). Rather than enforcing a single global style, derive colors, fonts, and layout patterns from context:
Expand Down
61 changes: 61 additions & 0 deletions .github/skills/experimental/powerpoint/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,63 @@ python scripts/render_pdf_images.py \

**Dependencies**: Requires LibreOffice for PPTX-to-PDF conversion and either `pdftoppm` (from `poppler`) or `pymupdf` (pip) for PDF-to-JPG rendering.

### Dry-Run Validation

```bash
python scripts/build_deck.py \
--content-dir content/ \
--style content/global/style.yaml \
Comment thread
auyidi1 marked this conversation as resolved.
--dry-run
```

Validates content files without producing a PPTX. Parses all `content.yaml` files, checks for speaker notes, runs AST validation on `content-extra.py` scripts, and counts image assets. Reports per-slide status and exits with code 1 if any errors are found.

### Generate Theme Variants

```bash
python scripts/generate_themes.py \
--content-dir content/ \
--themes themes.yaml \
--output-dir ../
```

Generates themed content directories from a base content directory using a color mapping YAML file. The themes YAML defines color replacement tables:

```yaml
themes:
fluent:
label: "Microsoft Fluent"
colors:
"1b1b1f": "FFFFFF"
"f8f8fc": "242424"
Comment thread
auyidi1 marked this conversation as resolved.
```

Each theme gets its own output directory with remapped `content.yaml`, `style.yaml`, and `content-extra.py` files. Images are copied as-is. Run `build_deck.py` on each themed directory to produce the PPTX.

### Embed Audio

```bash
python scripts/embed_audio.py \
--input slide-deck/presentation.pptx \
--audio-dir voice-over/ \
--output slide-deck/presentation-narrated.pptx
```

Embeds WAV audio files into PPTX slides. Audio files are matched to slides by naming convention (`slide-001.wav`, `slide-002.wav`, etc.). The audio icon is placed off-screen (below the slide boundary) to keep it hidden during presentation. Pass `--slides` to embed audio on specific slides only.

**Dependencies**: Requires `pillow` (`pip install pillow`) for poster frame generation.

### Export Slides to SVG

```bash
python scripts/export_svg.py \
--input slide-deck/presentation.pptx \
--output-dir slide-deck/svg/ \
--slides 3,5,10
```

Exports slides to SVG format via LibreOffice (PPTX → PDF) and PyMuPDF (PDF → SVG). Output files are named `slide-NNN.svg`. Pass `--slides` to export specific slides. **Dependencies**: Requires LibreOffice and `pymupdf`.

## Script Architecture

The build and extraction scripts use shared modules in the `scripts/` directory:
Expand All @@ -419,8 +476,12 @@ The build and extraction scripts use shared modules in the `scripts/` directory:
| `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

Expand Down
1 change: 1 addition & 0 deletions .github/skills/experimental/powerpoint/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ requires-python = ">=3.11"
dependencies = [
"python-pptx",
"pyyaml",
"ruamel.yaml",
"cairosvg",
Comment thread
auyidi1 marked this conversation as resolved.
Comment thread
auyidi1 marked this conversation as resolved.
"Pillow",
"pymupdf",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env pwsh
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
#Requires -Version 7.0

<#
.SYNOPSIS
Embed WAV audio files into a PowerPoint deck.

.DESCRIPTION
Wrapper script that manages the Python virtual environment and invokes
embed_audio.py to embed per-slide WAV files into a PPTX presentation.

.PARAMETER InputPath
Input PPTX file path.

.PARAMETER AudioDir
Directory containing slide-NNN.wav files.

.PARAMETER OutputPath
Output PPTX file path.

.PARAMETER Slides
Comma-separated slide numbers to embed audio on (optional).

.PARAMETER SkipVenvSetup
Skip virtual environment setup.

.PARAMETER Verbose
Enable verbose output.

.EXAMPLE
./Invoke-EmbedAudio.ps1 -InputPath deck.pptx -AudioDir voice-over/ -OutputPath out.pptx

.NOTES
Part of the powerpoint skill. Manages uv virtual environment setup
and delegates to embed_audio.py for WAV embedding into PPTX slides.
#>

[CmdletBinding()]
param(
Comment thread
auyidi1 marked this conversation as resolved.
[Parameter(Mandatory = $true)][string]$InputPath,
[Parameter(Mandatory = $true)][string]$AudioDir,
[Parameter(Mandatory = $true)][string]$OutputPath,
[string]$Slides,
[switch]$SkipVenvSetup
)

$ErrorActionPreference = 'Stop'
Comment thread
auyidi1 marked this conversation as resolved.

#region Environment Setup

$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$SkillRoot = Split-Path -Parent $ScriptDir
$VenvDir = Join-Path $SkillRoot '.venv'

#endregion Environment Setup

#region Main

if ($MyInvocation.InvocationName -ne '.') {

if (-not $SkipVenvSetup) {
if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
throw 'uv is required but was not found on PATH.'
}
uv sync --directory $SkillRoot
}

$python = if (Test-Path (Join-Path $VenvDir 'Scripts/python.exe')) {
Join-Path $VenvDir 'Scripts/python.exe'
} elseif (Test-Path (Join-Path $VenvDir 'bin/python')) {
Join-Path $VenvDir 'bin/python'
} else {
throw "Python interpreter not found in venv. Run: uv sync --directory `"$SkillRoot`""
}

$script = Join-Path $ScriptDir 'embed_audio.py'
$ScriptArgs = @($script, '--input', $InputPath, '--audio-dir', $AudioDir, '--output', $OutputPath)
if ($Slides) { $ScriptArgs += '--slides'; $ScriptArgs += $Slides }
if ($VerbosePreference -eq 'Continue') { $ScriptArgs += '-v' }

& $python @ScriptArgs
exit $LASTEXITCODE

}

#endregion Main
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env pwsh
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
#Requires -Version 7.0

<#
.SYNOPSIS
Export PowerPoint slides to SVG images.

.DESCRIPTION
Wrapper script that manages the Python virtual environment and invokes
export_svg.py to convert PPTX slides to SVG via LibreOffice and PyMuPDF.

.PARAMETER InputPath
Input PPTX file path.

.PARAMETER OutputDir
Output directory for SVG files.

.PARAMETER Slides
Comma-separated slide numbers to export (optional).

.PARAMETER SkipVenvSetup
Skip virtual environment setup.

.PARAMETER Verbose
Enable verbose output.

.EXAMPLE
./Invoke-ExportSvg.ps1 -InputPath deck.pptx -OutputDir svg/

.NOTES
Part of the powerpoint skill. Manages uv virtual environment setup
and delegates to export_svg.py for PPTX-to-SVG conversion.
#>

[CmdletBinding()]
param(
Comment thread
auyidi1 marked this conversation as resolved.
[Parameter(Mandatory = $true)][string]$InputPath,
[Parameter(Mandatory = $true)][string]$OutputDir,
[string]$Slides,
[switch]$SkipVenvSetup
)

$ErrorActionPreference = 'Stop'
Comment thread
auyidi1 marked this conversation as resolved.

#region Environment Setup

$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$SkillRoot = Split-Path -Parent $ScriptDir
$VenvDir = Join-Path $SkillRoot '.venv'

#endregion Environment Setup

#region Main

if ($MyInvocation.InvocationName -ne '.') {

if (-not $SkipVenvSetup) {
if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
throw 'uv is required but was not found on PATH.'
}
uv sync --directory $SkillRoot
}

$python = if (Test-Path (Join-Path $VenvDir 'Scripts/python.exe')) {
Join-Path $VenvDir 'Scripts/python.exe'
} elseif (Test-Path (Join-Path $VenvDir 'bin/python')) {
Join-Path $VenvDir 'bin/python'
} else {
throw "Python interpreter not found in venv. Run: uv sync --directory `"$SkillRoot`""
}

$script = Join-Path $ScriptDir 'export_svg.py'
$ScriptArgs = @($script, '--input', $InputPath, '--output-dir', $OutputDir)
if ($Slides) { $ScriptArgs += '--slides'; $ScriptArgs += $Slides }
if ($VerbosePreference -eq 'Continue') { $ScriptArgs += '-v' }

& $python @ScriptArgs
exit $LASTEXITCODE

}

#endregion Main
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env pwsh
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
#Requires -Version 7.0

<#
.SYNOPSIS
Generate themed content directory variants from a base deck.

.DESCRIPTION
Wrapper script that manages the Python virtual environment and invokes
generate_themes.py to produce themed content copies with remapped colors.

.PARAMETER ContentDir
Path to the base theme's content directory.

.PARAMETER ThemesPath
Path to a YAML file defining theme color mappings.

.PARAMETER OutputDir
Parent directory where themed content directories are created.

.PARAMETER SkipVenvSetup
Skip virtual environment setup.

.PARAMETER Verbose
Enable verbose output.

.EXAMPLE
./Invoke-GenerateThemes.ps1 -ContentDir content/ -ThemesPath themes.yaml -OutputDir ../

.NOTES
Part of the powerpoint skill. Manages uv virtual environment setup
and delegates to generate_themes.py for themed content generation.
#>

[CmdletBinding()]
param(
Comment thread
auyidi1 marked this conversation as resolved.
[Parameter(Mandatory = $true)][string]$ContentDir,
[Parameter(Mandatory = $true)][string]$ThemesPath,
[Parameter(Mandatory = $true)][string]$OutputDir,
[switch]$SkipVenvSetup
)

$ErrorActionPreference = 'Stop'
Comment thread
auyidi1 marked this conversation as resolved.

#region Environment Setup

$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$SkillRoot = Split-Path -Parent $ScriptDir
$VenvDir = Join-Path $SkillRoot '.venv'

#endregion Environment Setup

#region Main

if ($MyInvocation.InvocationName -ne '.') {

if (-not $SkipVenvSetup) {
if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
throw 'uv is required but was not found on PATH.'
}
uv sync --directory $SkillRoot
}

$python = if (Test-Path (Join-Path $VenvDir 'Scripts/python.exe')) {
Join-Path $VenvDir 'Scripts/python.exe'
} elseif (Test-Path (Join-Path $VenvDir 'bin/python')) {
Join-Path $VenvDir 'bin/python'
} else {
throw "Python interpreter not found in venv. Run: uv sync --directory `"$SkillRoot`""
}

$script = Join-Path $ScriptDir 'generate_themes.py'
$ScriptArgs = @($script, '--content-dir', $ContentDir, '--themes', $ThemesPath, '--output-dir', $OutputDir)
if ($VerbosePreference -eq 'Continue') { $ScriptArgs += '-v' }

& $python @ScriptArgs
exit $LASTEXITCODE

}

#endregion Main
Loading
Loading