v2.4.0 — build from the template: slide lifecycle, design guidance, previews, validation
The biggest release since the fork. It closes the gap between this server and what a
capable PowerPoint agent needs, and it does so around one premise: the deck the user
gets should be built out of their template, not painted over it.
Everything here is an independent implementation. Nothing was copied from the skill this
work was benchmarked against.
1. Slides can finally be moved, copied and duplicated
python-pptx's only entry point is slides.add_slide(layout). There was no delete, no
reorder, no duplicate — so the only way to build was to add bare slides and reconstruct
the template's look by hand, which never quite matches.
delete_slide, move_slide, duplicate_slide, copy_slide_between_presentations and
manage_speaker_notes are implemented at the OPC level: <p:sldIdLst> edits for delete
and move, and for duplication a deepcopy of the slide XML plus a rebuilt relationship set
that preserves the source's rIds — the copied XML still carries them, so letting the
package renumber breaks every r:embed. Image, media and font parts are shared;
chart, SmartArt and embedded parts are cloned, or update_chart_data on a copy would
rewrite the original's chart.
This makes the intended flow possible at last: duplicate the template slide, then fill
it. A layout holds placeholders. It does not hold the template's artwork.
2. The agent is told what a good deck looks like
The tools answered "how do I place this". Nothing answered "what should this slide look
like". get_design_guidance serves docs/DESIGN_GUIDANCE.md — nine sections on
structure, layout, type, colour, charts, images, the habits that make a deck read as
machine-made, and the build loop — whole or one section at a time, so it costs nothing
until a caller is actually building.
Its first section is the one that matters: this server's default case is a corporate
template, so the right move is to inherit the user's design, not to invent a palette over
their brand.
3. Two ways to actually see the deck
render_slide_previews renders the deck into labelled contact sheets and has the vision
model describe what each slide is structurally for. Layout names cannot tell you which of
eight near-identical layouts holds the three-card row. The agent cannot look at a picture,
so the description is the half it can act on.
render_deck_summary_card is the mirror image, for the end of the job: every slide
tiled into one image, attached beside the exported file so the user sees the result
without downloading anything. No slide cap, no vision call, and because the stored image
is the deliverable there, a failed upload is an error rather than a note. The column
count is fitted per deck — a fixed grid turns 60 slides into an unreadable stripe — and
cells shrink to a pixel budget except when that would make them illegible, in which case
the card grows taller instead. A card that scrolls beats one you cannot read.
4. Structural validation, because QA cannot see everything
A deck with a dangling rId renders fine in LibreOffice, opens fine in python-pptx, and
still fails in PowerPoint. validate_presentation reports what the visual axis is blind
to, with severities (error = may not open, warning = user-visible defect, info =
advisory) and a fix on every problem naming the tool that resolves it.
Export folds in a structure summary but does not block on it. Refusing to deliver a
finished deck over a warning costs the user more than the warning does.
5. Fonts: QA now knows when to doubt itself
Visual QA renders through LibreOffice, so its text-fit verdicts are only trustworthy for
fonts with metric-compatible substitutes (Liberation Sans/Serif/Mono, Carlito, Caladea).
Everything else is substituted by similarity and the widths differ — a screenshot can show
text overflowing a box that fits in PowerPoint.
Non-metric fonts now raise an info problem in validation and append a caveat to the
review prompt telling the reviewer to report only clear, substantial overflow for text in
those fonts. The point is not to "fix" a template's fonts to satisfy QA. The brand wins;
the caveat is what makes that possible.
6. Combo charts, secondary axes, per-series formatting
add_combo_chart covers what a single chart group cannot: bars with a target line across
them, or two measures whose units differ so much that one flattens to nothing on a shared
axis. format_chart_series adds colour, data labels, number formats and trendlines.
A plot area holds a list of chart-group elements, each naming its axis pair, so this
builds an ordinary single-type chart with every series — which is what writes a correct
embedded workbook and the shared category caches — then redistributes the c:ser elements
into per-(type, axis) groups. It never falls back to a rendered image: visual repair can
move or delete a picture, and nothing else.
One trap worth naming: chart.value_axis returns the second valAx when a chart has
two, because python-pptx assumes a scatter chart. Titling "the value axis" on a dual-axis
combo put the left axis' title on the right-hand one. Axes are addressed by id now.
7. PDF export and legacy .ppt input
export_presentation(format="pptx"|"pdf"|"both") — the pair is what a user who asked to
"share" a deck usually wants. .ppt uploads are converted on the way in, with an explicit
note that the conversion is approximate: the binary round-trip loses layouts, measured
11 → 1 on the demo deck.
Testing
302 tests, green on 3.10 and 3.12. LibreOffice was installed locally for this work, so
the renderer-dependent tests actually ran rather than self-skipping.
tests/test_render_integration.py puts the hand-assembled XML through a real renderer and
asserts on text extracted from the PDF, not image size. Its first version was vacuous:
a combo chart whose line group named an undeclared axis still passed an "is it blank"
check, because the columns render regardless. There is now a test that breaks the axis on
purpose and asserts the secondary tick labels disappear, so the class cannot silently go
vacuous again.
Full diff: v2.3.1...v2.4.0