Presentation Renderer
Child of presentation-builder. This skill handles the final compilation step: taking a structured slide outline (from presentation-narrative), visual assets (from presentation-datavis and presentation-diagrams), and styling tokens (from presentation-styling) and producing deliverable PPTX and/or HTML files.
Siblings: presentation-narrative, presentation-datavis, presentation-diagrams, presentation-styling.
When NOT to Use
- Content strategy or outline creation -- use
presentation-narrative - Chart/graph generation -- use
presentation-datavis - Architecture or flow diagrams -- use
presentation-diagrams - Theme design, color palettes, typography -- use
presentation-styling - Overall orchestration of a deck from scratch -- use
presentation-builder(parent)
Use this skill only when the outline and assets are ready and the task is to produce the final file(s).
1. Engine Selection
Select the rendering engine based on what is available in the environment. Always prefer the highest-ranked option that is confirmed present.
PPTX Fallback Chain
| Priority | Engine | Notes |
|---|---|---|
| 1 | python-pptx | Preferred. Native editable PPTX, full template support, cross-platform. |
| 2 | Apache POI XSLF | Java fallback. Requires JVM. |
| 3 | PowerShell COM | Windows-only. Requires PowerPoint installed. |
| 4 | Marp --pptx-editable |
Experimental. Markdown source required. |
| 5 | HTML only | If no PPTX engine is available, fall back to HTML output. |
HTML Fallback Chain
| Priority | Engine | Notes |
|---|---|---|
| 1 | reveal.js | Best for agent-controlled output. Offline capable. Single-file possible. |
| 2 | Marp | Fastest markdown-to-slides path. |
| 3 | Raw HTML | Always works. Inline CSS. No dependencies. |
Capability detection: Before rendering, verify the engine is available (e.g., python -c "import pptx", check for marp on PATH, check for Java). Record the result and proceed down the chain on failure.
2. Python-pptx Generation Pattern
This is the primary rendering path. All slide types follow the same structure: load template, add slide from layout, populate placeholders, save.
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.enum.text import PP_ALIGN
from pptx.dml.color import RGBColor
# Load template or create new
prs = Presentation("template.pptx") # or Presentation()
# --- Title Slide ---
slide = prs.slides.add_slide(prs.slide_layouts[0])
slide.shapes.title.text = "Presentation Title"
slide.placeholders[1].text = "Subtitle — Date — Author"
slide.notes_slide.notes_text_frame.text = "Opening remarks and context."
# --- Content Slide (action title + bullets) ---
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = "Key takeaway as the title"
body = slide.placeholders[1]
tf = body.text_frame
tf.text = "Supporting point 1"
p = tf.add_paragraph()
p.text = "Supporting point 2"
p.level = 0
slide.notes_slide.notes_text_frame.text = "Expand on points 1 and 2."
# --- Two-Column Slide ---
slide = prs.slides.add_slide(prs.slide_layouts[3]) # two-content layout
slide.shapes.title.text = "Comparison Title"
left = slide.placeholders[1]
left.text_frame.text = "Left column content"
right = slide.placeholders[2]
right.text_frame.text = "Right column content"
slide.notes_slide.notes_text_frame.text = "Compare left vs right."
# --- Image / Chart Slide ---
slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank layout
slide.shapes.title.text = "Chart Title" # add title textbox if blank layout
slide.shapes.add_picture(
"chart.png",
Inches(1), Inches(1.5),
Inches(8), Inches(5)
)
# Add source line
from pptx.util import Pt
txBox = slide.shapes.add_textbox(Inches(1), Inches(6.6), Inches(8), Inches(0.3))
txBox.text_frame.text = "Source: Internal analytics, Q4 2025"
txBox.text_frame.paragraphs[0].font.size = Pt(8)
txBox.text_frame.paragraphs[0].font.color.rgb = RGBColor(0x80, 0x80, 0x80)
slide.notes_slide.notes_text_frame.text = "Explain chart trends and data source."
# --- Table Slide ---
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = "Data Summary"
rows, cols = 4, 3
table_shape = slide.shapes.add_table(rows, cols, Inches(1), Inches(2), Inches(8), Inches(3.5))
table = table_shape.table
# Header row
for col_idx, header in enumerate(["Metric", "Q3", "Q4"]):
table.cell(0, col_idx).text = header
# Data rows
for row_idx, row_data in enumerate([
["Revenue", "$1.2M", "$1.5M"],
["Users", "10K", "14K"],
["NPS", "72", "78"],
], start=1):
for col_idx, val in enumerate(row_data):
table.cell(row_idx, col_idx).text = val
slide.notes_slide.notes_text_frame.text = "Walk through each metric row."
# --- Section Divider ---
slide = prs.slides.add_slide(prs.slide_layouts[2]) # section header layout
slide.shapes.title.text = "Section 2: Deep Dive"
if slide.placeholders.get(1):
slide.placeholders[1].text = "Transition message"
slide.notes_slide.notes_text_frame.text = "Pause before transitioning."
# --- Closing Slide ---
slide = prs.slides.add_slide(prs.slide_layouts[0])
slide.shapes.title.text = "Thank You"
slide.placeholders[1].text = "Questions? — contact@example.com"
slide.notes_slide.notes_text_frame.text = "Open the floor for Q&A."
# Save
prs.save("output.pptx")
Layout index reference (common defaults; always verify against the loaded template):
| Index | Typical Layout |
|---|---|
| 0 | Title Slide |
| 1 | Title and Content |
| 2 | Section Header |
| 3 | Two Content |
| 4 | Comparison |
| 5 | Title Only |
| 6 | Blank |
3. reveal.js HTML Generation Pattern
Generate a single self-contained HTML file. For offline delivery, embed CSS and JS inline.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Presentation Title</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/reveal.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/theme/white.css">
<style>
/* Custom overrides from presentation-styling tokens */
.reveal h1, .reveal h2 { font-family: 'Inter', sans-serif; }
.reveal .source-line { font-size: 0.5em; color: #888; text-align: right; }
</style>
</head>
<body>
<div class="reveal"><div class="slides">
<!-- Title Slide -->
<section>
<h1>Presentation Title</h1>
<p>Subtitle — Date — Author</p>
<aside class="notes">Opening remarks and context.</aside>
</section>
<!-- Content Slide -->
<section>
<h2>Key Takeaway as the Title</h2>
<ul>
<li>Supporting point 1</li>
<li>Supporting point 2</li>
</ul>
<aside class="notes">Expand on points 1 and 2.</aside>
</section>
<!-- Two-Column Slide -->
<section>
<h2>Comparison Title</h2>
<div style="display: flex; gap: 2em;">
<div style="flex: 1;"><h3>Left</h3><p>Left column content</p></div>
<div style="flex: 1;"><h3>Right</h3><p>Right column content</p></div>
</div>
<aside class="notes">Compare left vs right.</aside>
</section>
<!-- Chart Slide -->
<section>
<h2>Chart Title</h2>
<img src="chart.png" alt="Chart description" style="max-height: 70vh;">
<p class="source-line">Source: Internal analytics, Q4 2025</p>
<aside class="notes">Explain chart trends and data source.</aside>
</section>
<!-- Section Divider -->
<section data-background-color="#003366">
<h2 style="color: white;">Section 2: Deep Dive</h2>
<aside class="notes">Pause before transitioning.</aside>
</section>
<!-- Closing Slide -->
<section>
<h1>Thank You</h1>
<p>Questions? — contact@example.com</p>
<aside class="notes">Open the floor for Q&A.</aside>
</section>
</div></div>
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/reveal.js"></script>
<script>
Reveal.initialize({
hash: true,
slideNumber: true,
showNotes: false, // set true for presenter view
transition: 'slide'
});
</script>
</body>
</html>
Offline variant: Replace CDN links with inline <style> and <script> blocks containing the full reveal.js CSS and JS. This produces a single portable HTML file.
4. Marp Markdown Pattern
Use when Marp CLI is available and markdown source is preferred.
---
marp: true
theme: default
paginate: true
header: "Company Name"
footer: "Confidential"
---
# Presentation Title
Subtitle — Date — Author
<!-- Speaker notes: Opening remarks and context. -->
---
## Key Takeaway as the Title
- Supporting point 1
- Supporting point 2
<!-- Speaker notes: Expand on points 1 and 2. -->
---
<!-- _class: lead -->
## Section 2: Deep Dive
<!-- Speaker notes: Pause before transitioning. -->
---
## Chart Title

<small>Source: Internal analytics, Q4 2025</small>
<!-- Speaker notes: Explain chart trends and data source. -->
---
## Data Summary
| Metric | Q3 | Q4 |
|---------|-------|-------|
| Revenue | $1.2M | $1.5M |
| Users | 10K | 14K |
<!-- Speaker notes: Walk through each metric row. -->
---
# Thank You
Questions? — contact@example.com
<!-- Speaker notes: Open the floor for Q&A. -->
Render commands:
marp deck.md -o deck.html # HTML slides
marp deck.md -o deck.pptx # PPTX (experimental)
marp deck.md -o deck.pdf # PDF
marp deck.md --html -o deck.html # HTML with raw HTML tags enabled
5. Apache POI Pattern (Java)
Use as a PPTX fallback when python-pptx is unavailable but a JVM is present.
import org.apache.poi.xslf.usermodel.*;
import java.io.FileOutputStream;
import java.awt.Rectangle;
XMLSlideShow ppt = new XMLSlideShow();
// Title slide
XSLFSlideLayout titleLayout = ppt.getSlideMasters().get(0)
.getLayout(SlideLayout.TITLE);
XSLFSlide slide = ppt.createSlide(titleLayout);
slide.getPlaceholder(0).setText("Presentation Title");
slide.getPlaceholder(1).setText("Subtitle — Date — Author");
XSLFNotes notes = ppt.getNotesSlide(slide);
notes.getPlaceholder(1).setText("Opening remarks.");
// Content slide
XSLFSlideLayout contentLayout = ppt.getSlideMasters().get(0)
.getLayout(SlideLayout.TITLE_AND_CONTENT);
XSLFSlide contentSlide = ppt.createSlide(contentLayout);
contentSlide.getPlaceholder(0).setText("Key Takeaway");
XSLFTextShape body = contentSlide.getPlaceholder(1);
body.clearText();
body.addNewTextParagraph().addNewTextRun().setText("Point 1");
body.addNewTextParagraph().addNewTextRun().setText("Point 2");
// Image slide
XSLFSlide imgSlide = ppt.createSlide();
byte[] imgData = java.nio.file.Files.readAllBytes(
java.nio.file.Paths.get("chart.png"));
XSLFPictureData pd = ppt.addPicture(imgData, PictureData.PictureType.PNG);
imgSlide.createPicture(pd)
.setAnchor(new Rectangle(72, 108, 576, 360));
// Save
try (FileOutputStream out = new FileOutputStream("output.pptx")) {
ppt.write(out);
}
ppt.close();
6. PowerShell COM Pattern (Windows)
Use only on Windows with PowerPoint installed. Note: colors use BGR order, not RGB.
$ppt = New-Object -ComObject PowerPoint.Application
$ppt.Visible = $true # set $false for headless
$pres = $ppt.Presentations.Add()
# Title slide (layout enum: ppLayoutTitle = 1)
$slide = $pres.Slides.Add(1, 1)
$slide.Shapes.Title.TextFrame.TextRange.Text = "Presentation Title"
$slide.Shapes.Placeholders.Item(2).TextFrame.TextRange.Text = "Subtitle"
$slide.NotesPage.Shapes.Placeholders.Item(2).TextFrame.TextRange.Text = "Opening remarks."
# Content slide (ppLayoutText = 2)
$slide2 = $pres.Slides.Add(2, 2)
$slide2.Shapes.Title.TextFrame.TextRange.Text = "Key Takeaway"
$body = $slide2.Shapes.Placeholders.Item(2).TextFrame.TextRange
$body.Text = "Point 1`r`nPoint 2"
$slide2.NotesPage.Shapes.Placeholders.Item(2).TextFrame.TextRange.Text = "Details."
# Image slide (ppLayoutBlank = 12)
$slide3 = $pres.Slides.Add(3, 12)
$slide3.Shapes.AddPicture(
"$PWD\chart.png", # file path
$false, # LinkToFile
$true, # SaveWithDocument
72, 108, 576, 360 # Left, Top, Width, Height (points)
)
# Save and close
$pres.SaveAs("$PWD\output.pptx")
$pres.Close()
$ppt.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ppt) | Out-Null
BGR color note: When setting font or shape colors via COM, use BGR hex (e.g., $shape.TextFrame.TextRange.Font.Color.RGB = 0xBB6600 for RGB #0066BB).
7. Image Embedding
All visual assets are produced by sibling skills and written to <project>/.presentations/output/assets/.
| Source Skill | Asset Type | Embed Method |
|---|---|---|
presentation-datavis |
Chart PNG | add_picture() at specified position |
presentation-diagrams |
Diagram SVG/PNG | add_picture() (convert SVG to PNG if engine requires) |
presentation-styling |
Logo PNG | Place per layout rules (e.g., bottom-right corner) |
Rules:
- Reference all images by absolute file path from
.presentations/output/assets/. - For PPTX, images are embedded in the file (no external references).
- For HTML, images can be embedded as base64 data URIs for offline portability, or referenced by relative path.
- SVG is preferred for diagrams in HTML; convert to PNG for PPTX (python-pptx does not support SVG natively).
8. Speaker Notes
Speaker notes are mandatory on every slide, including section dividers and closing slides.
| Engine | Method |
|---|---|
| python-pptx | slide.notes_slide.notes_text_frame.text = "..." |
| reveal.js | <aside class="notes">...</aside> inside <section> |
| Marp | HTML comment: <!-- Speaker notes: ... --> |
| Apache POI | ppt.getNotesSlide(slide).getPlaceholder(1).setText("...") |
| PowerShell COM | $slide.NotesPage.Shapes.Placeholders.Item(2).TextFrame.TextRange.Text = "..." |
Content guidance: Notes should contain talking points, not a script. Include: what to emphasize, data source references, transition cues.
9. Output Management
File Naming
YYYY-MM-DD_topic-slug.pptx
YYYY-MM-DD_topic-slug.html
Example: 2026-03-27_quarterly-review.pptx
Output Directory
<project>/.presentations/output/
<project>/.presentations/output/assets/ # charts, diagrams, logos
Post-Render Checklist
- Verify output file exists at the expected path.
- Confirm file size is non-zero.
- Generate both PPTX and HTML when the capability map supports it.
- Present the final file path(s) to the user.
import os
output_path = ".presentations/output/2026-03-27_quarterly-review.pptx"
assert os.path.exists(output_path), f"Output not found: {output_path}"
assert os.path.getsize(output_path) > 0, f"Output is empty: {output_path}"
print(f"Rendered: {os.path.abspath(output_path)} ({os.path.getsize(output_path):,} bytes)")
10. HARD RULES
- Never overwrite existing output without user confirmation. Check if the target file exists before saving. If it does, ask the user or append a version suffix.
- Always validate output file exists and is non-zero. Run the post-render checklist after every save operation.
- Template must be loaded before rendering. Do not hard-code slide dimensions, fonts, or colors. Load these from the template PPTX or the styling tokens provided by
presentation-styling. - Speaker notes on every slide. No exceptions -- even section dividers and closing slides must have at least a brief note.
- Source line on every data chart slide. Any slide containing a chart, graph, or data table must include a visible source attribution (small text, bottom of slide).
Anti-Patterns
| Anti-Pattern | Why It Fails | Correct Approach |
|---|---|---|
| Rendering to PPTX without testing on the target display system | Fonts, animations, and layouts render differently on Mac/Windows/web; broken slides in the meeting | Test rendered output on the actual presentation system; embed fonts; convert custom fonts to paths if needed |
| Including high-resolution images without compression | 50MB+ PPTX files that crash email, take minutes to load, and lag during presentation | Compress images to 150-200 DPI for screen; strip EXIF data; target under 10MB for emailable decks |
| Not including speaker notes in the rendered output | Presenter deck and handout deck are different; rendered file without notes is incomplete | Always include speaker notes in PPTX; generate a separate notes-only PDF for the presenter |
| Rendering without checking slide master consistency | Mixed slide masters produce inconsistent headers, footers, page numbers, and branding | Validate all slides reference the same slide master/layout before rendering; fix orphaned layouts |
| Skipping accessibility checks on final output | Screen readers cannot parse image-heavy slides; colorblind users miss color-coded information | Add alt text to all images; ensure sufficient color contrast; test with accessibility checker |