Add comprehensive showcase decks demonstrating post-fork features#17
Add comprehensive showcase decks demonstrating post-fork features#17CodeHalwell merged 4 commits intomasterfrom
Conversation
…humbnails Six small, self-contained example decks that each isolate one feature area (design tokens + recipes, charts, visual effects, animations, space-aware authoring, tables) plus a build_all script that renders one PNG per slide via PDF + pdftoppm so reviewers can scan the suite without opening PowerPoint. https://claude.ai/code/session_01TTPAcGiq4DL9nzBGng94Pt
…visible The left "naive Pt(36)" box was inheriting add_textbox's default auto_size=SHAPE_TO_FIT_TEXT and silently growing to fit, and font size was being set on the paragraph (no run yet) instead of the run itself, so it never actually applied. Both boxes ended up rendering identically — the demo wasn't showing anything. Fix: pin auto_size=NONE on the naive box, style the runs explicitly, and tighten the box height to 2.0 inches so 36pt visibly overflows and fit_text has to drop the size to 30pt to make it fit. The 6pt difference is now obvious at a glance. https://claude.ai/code/session_01TTPAcGiq4DL9nzBGng94Pt
The previous 2.0in box was tall enough that fit_text picked 30pt and then the renderer wrapped to 5 lines, which still overflowed — there was a small discrepancy between fit_text's measurement and the LibreOffice renderer's wrap width. With a 1.5in box, fit_text picks 22pt (a 14pt drop from the naive 36pt) and the result fits cleanly. Also added auto_size=TEXT_TO_FIT_SHAPE on the fit_text box as belt-and-braces, in case the renderer disagrees with fit_text's Pillow-based measurement by a couple of points. https://claude.ai/code/session_01TTPAcGiq4DL9nzBGng94Pt
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 749d5d997d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if shape.shape_type == MSO_SHAPE.ROUNDED_RECTANGLE: | ||
| Emphasis.pulse(s3, shape) |
There was a problem hiding this comment.
Use auto_shape_type when selecting KPI cards
The KPI pulse loop never matches any shape because shape.shape_type returns a MSO_SHAPE_TYPE value (e.g. AUTO_SHAPE), but the code compares it to MSO_SHAPE.ROUNDED_RECTANGLE (an auto-shape subtype). In this slide, that makes the Emphasis.pulse(...) calls unreachable, so the deck does not actually demonstrate the per-card emphasis animation it advertises. Filter with shape.auto_shape_type (or check shape_type == AUTO_SHAPE first) so the KPI cards are animated.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive showcase suite for the power-pptx library, featuring six example scripts that demonstrate core functionalities such as design tokens, chart palettes, visual effects, animations, and space-aware text fitting. The suite is supported by build automation, shared design tokens, and a linting utility. Review feedback focuses on cleaning up an unnecessary lint suppression and improving error handling within the build process.
| from pptx.enum.text import MSO_AUTO_SIZE | ||
| from pptx.util import Inches, Pt | ||
|
|
||
| from _tokens import BRAND # noqa: F401 (kept for parity) |
| def _load(name: str): | ||
| path = HERE / f"{name}.py" | ||
| spec = importlib.util.spec_from_file_location(name, path) | ||
| assert spec and spec.loader |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR introduces a self-contained “showcase” suite under examples/showcase/ to demonstrate post-fork power-pptx features (design tokens/recipes, charts, visual effects, animations, space-aware authoring + linting, and tables), alongside build tooling and documentation.
Changes:
- Added six runnable showcase deck scripts (
01_...py→06_...py) that build small, focused.pptxoutputs. - Added shared helpers for consistent branding (
_tokens.py), lint-on-save (_lint.py), and an all-decks build + thumbnail renderer (build_all.py). - Added README plus an asset generator script for the synthetic hero image.
Reviewed changes
Copilot reviewed 12 out of 41 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/showcase/build_all.py | Builds all showcase decks and renders per-slide thumbnails via LibreOffice + pdftoppm. |
| examples/showcase/assets/_make_assets.py | Generates the synthetic hero.jpg asset used by decks. |
| examples/showcase/_tokens.py | Defines shared DesignTokens and chart palette for consistent branding. |
| examples/showcase/_lint.py | Adds a shared “lint then fail on residual errors” helper. |
| examples/showcase/README.md | Documents how to build/run decks and what each deck demonstrates. |
| examples/showcase/01_design_system.py | Deck demonstrating tokens + recipes + Grid layout. |
| examples/showcase/02_charts.py | Deck demonstrating chart palettes, quick layouts, and per-series/point fills. |
| examples/showcase/03_visual_effects.py | Deck demonstrating shadows, glows, gradients, and alpha fills. |
| examples/showcase/04_animations.py | Deck demonstrating animations, motion paths, and transitions. |
| examples/showcase/05_space_aware.py | Deck demonstrating fit_text, auto-size fallback, and the linter + auto-fix. |
| examples/showcase/06_tables.py | Deck demonstrating branded tables and the Cell.borders API. |
| examples/showcase/.gitignore | Ignores Python bytecode/cache in the showcase directory. |
Comments suppressed due to low confidence (6)
examples/showcase/build_all.py:1
assert spec and spec.loadercan be stripped when Python is run with optimizations (-O), which would turn an import failure into a more confusing runtime error downstream. Prefer an explicit check that raises a descriptive exception (e.g.,ImportError/FileNotFoundError) including the script path and module name.
examples/showcase/build_all.py:1- If
sub/already contains olderslide-*.pngfrom a previous run (especially when a deck’s slide count decreases), the finalglobwill include stale thumbnails and the reported count will be wrong. Before runningpdftoppm, delete existingslide-*.pnginsub(or render into a temporary directory and replace) to ensure the output accurately reflects the current deck.
examples/showcase/build_all.py:1 - If
sub/already contains olderslide-*.pngfrom a previous run (especially when a deck’s slide count decreases), the finalglobwill include stale thumbnails and the reported count will be wrong. Before runningpdftoppm, delete existingslide-*.pnginsub(or render into a temporary directory and replace) to ensure the output accurately reflects the current deck.
examples/showcase/build_all.py:1 - If
sub/already contains olderslide-*.pngfrom a previous run (especially when a deck’s slide count decreases), the finalglobwill include stale thumbnails and the reported count will be wrong. Before runningpdftoppm, delete existingslide-*.pnginsub(or render into a temporary directory and replace) to ensure the output accurately reflects the current deck.
examples/showcase/build_all.py:1 - Both external conversions can hang indefinitely in some environments (LibreOffice profile/locking issues, font problems, etc.). Consider adding a reasonable
timeout=and including a short excerpt of both stdout/stderr in the failure message to make CI and local troubleshooting more reliable.
examples/showcase/build_all.py:1 - Both external conversions can hang indefinitely in some environments (LibreOffice profile/locking issues, font problems, etc.). Consider adding a reasonable
timeout=and including a short excerpt of both stdout/stderr in the failure message to make CI and local troubleshooting more reliable.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for shape in s3.shapes: | ||
| if shape.shape_type == MSO_SHAPE.ROUNDED_RECTANGLE: | ||
| Emphasis.pulse(s3, shape) |
There was a problem hiding this comment.
shape.shape_type is the high-level shape category (e.g., auto-shape vs placeholder), not the specific auto-shape kind. Comparing it to MSO_SHAPE.ROUNDED_RECTANGLE will likely never match, so the pulse effect may not be applied to any KPI cards. Use the auto-shape discriminator (commonly shape.auto_shape_type) and/or guard for auto-shapes before comparing to MSO_SHAPE.ROUNDED_RECTANGLE.
| badge.line.fill.background() | ||
| badge.text_frame.text = "Go" | ||
| p = badge.text_frame.paragraphs[0] | ||
| p.alignment = 2 # center |
There was a problem hiding this comment.
Using a numeric literal for alignment makes the intent and enum mapping unclear and brittle. Prefer the appropriate enum constant (e.g., PP_ALIGN.CENTER) by importing it from pptx.enum.text.
| "This text is set at 28pt without word-wrapping a long enough string " | ||
| "to overflow the height — fit_text would prevent this, but we're " | ||
| "skipping it on purpose to show the lint output." | ||
| ) | ||
| otf.paragraphs[0].font.size = Pt(28) | ||
| otf.paragraphs[0].font.color.rgb = NEUTRAL |
There was a problem hiding this comment.
The demo text says it’s 'without word-wrapping' but otf.word_wrap = True, which makes the explanation misleading. Also, earlier in this file you note paragraph-level font settings may not apply to existing runs; here you set otf.text (creating a run) and then set paragraph.font.size, which may fail to enforce 28pt and weaken the intended overflow/lint demonstration. Update the explanatory text (or the word_wrap setting) and apply font size/color to the created run to ensure the slide reliably triggers the linter as intended.
| "This text is set at 28pt without word-wrapping a long enough string " | |
| "to overflow the height — fit_text would prevent this, but we're " | |
| "skipping it on purpose to show the lint output." | |
| ) | |
| otf.paragraphs[0].font.size = Pt(28) | |
| otf.paragraphs[0].font.color.rgb = NEUTRAL | |
| "This text is set at 28pt with word-wrapping and uses a long enough " | |
| "string to overflow the height — fit_text would prevent this, but " | |
| "we're skipping it on purpose to show the lint output." | |
| ) | |
| p = otf.paragraphs[0] | |
| run = p.runs[0] | |
| run.font.size = Pt(28) | |
| run.font.color.rgb = NEUTRAL |
| errors: list[str] = [] | ||
| for i, slide in enumerate(prs.slides): | ||
| for issue in slide.lint().issues: | ||
| if issue.severity.value == "error": |
There was a problem hiding this comment.
Comparing issue.severity.value to the string 'error' is brittle (it depends on the enum’s .value representation). Prefer comparing against the enum member itself (or a stable attribute like .name) to avoid breakage if the enum changes its underlying values.
| if issue.severity.value == "error": | |
| if issue.severity.name.lower() == "error": |
Fixes: * 04_animations.py: the KPI pulse loop never matched any shape because shape_type returns AUTO_SHAPE (the high-level category), not the specific subtype — pulses were silently no-ops. Filter on shape_type first, then auto_shape_type. Three Pulse effects now actually emit XML on the KPI slide. (Codex P2 / Copilot) * 04_animations.py: replace magic `p.alignment = 2` with PP_ALIGN.CENTER and apply font to the badge's run, not the paragraph. (Copilot) * _lint.py: compare severity against LintSeverity.ERROR enum directly rather than its `.value` string — robust if the enum representation changes. (Copilot) * 05_space_aware.py: drop the unused `BRAND` import (the "kept for parity" comment was dead). (Gemini) * 05_space_aware.py: fix the lint-demo overflow box — pin auto_size=NONE, apply font/color to the run (paragraph-level properties don't reach already-authored runs), and rewrite the misleading "without word-wrapping" demo text. The lint pass now catches both the off-slide rectangle AND the text overflow as intended. (Copilot) * build_all.py: replace `assert spec and spec.loader` with an explicit RuntimeError that survives `python -O`. (Gemini) * build_all.py: clean stale `slide-*.png` before pdftoppm so a shrunken deck doesn't leave orphans inflating the count; add 120s/60s subprocess timeouts and surface stderr/stdout excerpts on failure. (Copilot, suppressed) https://claude.ai/code/session_01TTPAcGiq4DL9nzBGng94Pt
Summary
This PR adds a complete suite of six self-contained example decks that comprehensively demonstrate every post-fork feature of power-pptx in a single, brand-aligned identity. The showcase covers design tokens & recipes, charts with palettes and gradients, visual effects (shadows, glows, gradients, alpha), animations & transitions, space-aware authoring with the linter, and branded tables with custom borders.
Each deck is small (1–6 slides) and isolates one feature area so the output is easy to review. All scripts share a common design-token spec (
_tokens.py) and a lint-on-save helper (_lint.py). A build script (build_all.py) generates all decks and renders PNG thumbnails via LibreOffice + pdftoppm.Changes
Six showcase decks (
01_design_system.pythrough06_tables.py):01_design_system.py: Token-driven recipes (title, kpi, bullet, quote, image_hero) + Grid-laid feature cards02_charts.py: Chart palettes, quick layouts, per-series gradients, per-data-point coloring03_visual_effects.py: Shadows, glows, soft edges, linear/radial/shape gradients, alpha-tinted glass04_animations.py: Sequenced entrance/emphasis, per-paragraph reveal, motion paths, transitions05_space_aware.py:fit_textvs naive sizing,TEXT_TO_FIT_SHAPEfallback, linter detection + auto-fix demo06_tables.py: Branded table withCell.borders, alternating row fill, conditional delta coloringShared utilities:
_tokens.py: SingleDesignTokensspec (palette, typography, shadows, radii, spacings, chart palette)_lint.py:lint_or_die()helper for consistent pre-save validationbuild_all.py: Build all decks and render thumbnails (requires LibreOffice + pdftoppm)assets/_make_assets.py: Generate synthetic hero image (radial gradient backdrop)Documentation:
README.md: Overview of each deck, build instructions, what to look for in eachPre-built outputs:
.pptxfiles in_out/_out/thumbs/Test notes
The showcase decks are self-validating: each runs the linter before save (via
lint_or_dieor inlineslide.lint().auto_fix()), ensuring all generated presentations are well-formed. The05_space_awaredeck deliberately includes a broken slide to demonstrate linter detection and auto-fix.To verify:
Each script prints the output path and any lint findings. All decks should save without errors (except
05_space_aware, which prints residual issues after auto-fix as part of the demo).https://claude.ai/code/session_01TTPAcGiq4DL9nzBGng94Pt