| name | document-skills-pptx |
| description | Create, read, edit, or analyze PowerPoint .pptx presentations. Use this Skill whenever the task involves slides or a deck - building a presentation from an outline or content file; editing text, tables, or charts in an existing deck; extracting slide content for review or repurposing; checking that a produced deck opens, has the right slide count, and nothing overflows its slide. |
PPTX workbench
A .pptx is a ZIP of XML parts (one per slide) plus masters, layouts, and media. python-pptx
models most of it safely; everything else is surgical.
Step 0 - Check the toolchain
python -c "import pptx; print('python-pptx ok')"
soffice present -> the rendered-PDF smoke test becomes available; use it for anything visual.
Step 1 - Classify the task
Step 2 - Shared rules
- Templates first: if the user provides a
.pptx template or brand deck, build on it
(Presentation("template.pptx")), reuse its masters/layouts, and never restyle globally.
- Layouts carry design: pick the closest built-in layout for each slide's purpose instead
of hand-placing empty text boxes. Hand-placement is for exceptions, measured.
- Text must fit its box: estimate width (chars x ~0.5 x font size for sans-serif at a
first approximation) and step font size down (never below 12pt body) or cut words. Overflow
text is a defect the postcheck must catch.
- Placeholders keep semantics: write into placeholder shapes (
.placeholders) when
available so title/body roles survive round-trips.
- Images: set both width and height from the real aspect ratio; never stretch.
- Charts: prefer a real chart part (
chart_data + add_chart) over a picture of a chart -
only a real chart stays editable and data-accurate.
- Output to a new path (
-edited suffix) unless in-place was explicitly requested.
Step 3 - Postcheck (mandatory)
from pptx import Presentation
from pptx.enum.shapes import MSO_SHAPE_TYPE
def iter_text_frames(shapes, path=""):
"""Yield every logical text frame, including group members and table cells."""
for shape in shapes:
here = f"{path}/{shape.name}" if path else shape.name
if shape.shape_type == MSO_SHAPE_TYPE.GROUP:
yield from iter_text_frames(shape.shapes, here)
continue
if shape.has_text_frame:
yield here, shape.text_frame
if shape.has_table:
for row_index, row in enumerate(shape.table.rows):
for column_index, cell in enumerate(row.cells):
if cell.is_spanned:
continue
yield f"{here}/table[{row_index},{column_index}]", cell.text_frame
prs = Presentation("output.pptx")
print("slides:", len(prs.slides))
for i, slide in enumerate(prs.slides):
inventory = [(location, frame.text) for location, frame in iter_text_frames(slide.shapes)]
print(i, len(slide.shapes), inventory)
intentionally_empty_locations = set()
unexpected_empty_locations = [
(slide_index, location)
for slide_index, slide in enumerate(prs.slides)
for location, frame in iter_text_frames(slide.shapes)
if not frame.text.strip()
and (slide_index, location) not in intentionally_empty_locations
]
if unexpected_empty_locations:
raise ValueError(f"unexpected empty text frames: {unexpected_empty_locations}")
Confirm: slide count matches the outline; every slide has its intended title text; no text
frame is empty that should not be. If soffice exists, render to PDF and check the page count
equals the slide count:
soffice --headless --convert-to pdf output.pptx --outdir <tmp>
Page count is only a structural smoke test. Rasterize and inspect every rendered slide for
horizontal clipping and for a final line clipped or missing at the bottom; follow the text-fit
procedure in references/analyze.md. Fix and render again if any text
overflows. If no production-equivalent renderer is available, report overflow as unverified -
do not claim that shape bounds or page count prove that text fits.
Report: output path, slide inventory (index, layout name, title), verification done, and any
remaining layout risks (long CJK strings, tight two-column slides).