Files
playbook/antigravity-awesome-skills/skills/pptx-deck-creation/references/reference-deck-analysis-patterns.md
T
2026-07-20 00:03:02 +00:00

151 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Reference-Deck Analysis Patterns
It describes how to approach PPTX extraction and style analysis with `python-pptx`, using short illustrative
snippets — not a packaged module to copy wholesale.
- Do not treat these snippets as bundled runtime modules.
- Do not import from this file or recreate `.py` files under this skill as packaged resources.
- When a task needs extraction or style analysis, write task-local code with `python-pptx`, using these patterns as reference.
## Shared constants
Most helpers depend on two values: the EMU-per-inch conversion factor and the
DrawingML namespace used when reading raw OOXML.
```python
EMU_PER_INCH = 914400
DRAWING_NS = "{http://schemas.openxmlformats.org/drawingml/2006/main}"
def _inches(value: int) -> float:
return round(int(value or 0) / EMU_PER_INCH, 4)
```
## Style master: compact design context
**Goal:** summarize a reference deck into a compact "style master" (colors,
fonts, font sizes, shape styles, layout/region usage) suitable for prompting a
generator. Counters drive a `most_common` ranking; results are truncated by
`max_items`.
### Approach
1. Open with `Presentation(path)`; read slide size in inches.
2. Pull theme tokens (colors/fonts) directly from `ppt/theme/theme1.xml` via `zipfile`.
3. Walk each slide's shapes (recursing into groups), tallying tokens into `Counter`s.
4. Emit `styles`, `brands`, `template`, and `layout` sections from the top-N tallies.
### Illustrative snippet
```python
from collections import Counter
def analyze(presentation) -> dict:
colors: Counter[str] = Counter()
fonts: Counter[str] = Counter()
for slide in presentation.slides:
for shape in _iter_shapes(slide.shapes):
colors.update(_shape_colors(shape).values())
fonts.update(_text_styles(shape)["fonts"])
return {
"colors": [{"value": v, "count": c} for v, c in colors.most_common(10)],
"fonts": [{"value": v, "count": c} for v, c in fonts.most_common(10)],
}
```
### Key helper patterns
- **Recursive shape walk** — yield each shape, then recurse when it exposes `.shapes`:
```python
def _iter_shapes(shapes):
for shape in shapes:
yield shape
if hasattr(shape, "shapes"):
yield from _iter_shapes(shape.shapes)
```
- **Color normalization** — read `color.rgb`, fall back to `theme_color`, and
normalize to `#RRGGBB` or `theme:<token>`.
- **Region/flow classification** — bucket each shape's bbox center into
`top/middle/bottom` × `left/center/right`, and infer `row` / `column` / `grid`
from the spread of centers.
- **Neutral filtering** — treat colors with low channel spread (`max-min <= 18`)
as neutrals so brand accents rank above grays.
## Extractor: structured deck capture
**Goal:** turn a deck into a structured tree of slides → groups → objects, with
optional media extraction and a parallel list of raw OOXML render elements.
### Approach
1. For each slide, build a root group bbox covering the full slide.
2. Walk shapes recursively; groups become nested groups, leaf shapes become objects.
3. Classify each object by `kind` (`text`, `table`, `image`, `chart`, `connector`, …)
and capture kind-specific `content` plus `style`.
4. Optionally write media to an asset dir, or base64-embed when no dir is given.
5. Read speaker notes by resolving slide `_rels` to their `notesSlide` parts.
### Illustrative snippet
```python
def _bbox(shape) -> dict:
return {
"x": _inches(getattr(shape, "left", 0)),
"y": _inches(getattr(shape, "top", 0)),
"width": _inches(getattr(shape, "width", 0)),
"height": _inches(getattr(shape, "height", 0)),
}
def _kind(shape, shape_type: str) -> str:
if getattr(shape, "has_table", False):
return "table"
if getattr(shape, "has_chart", False):
return "chart"
if "picture" in shape_type or getattr(shape, "image", None):
return "image"
if getattr(shape, "has_text_frame", False) and shape.text.strip():
return "text"
return "shape"
```
### Kind-specific content notes
- **text** — capture `text` plus rich paragraphs (runs with font size, bold,
italic, color, hyperlink, paragraph alignment/level).
- **table** — emit `rows`, plus row/col counts, widths/heights, banding flags,
and merged-cell origins (`span_rows` / `span_cols`).
- **image** — record alt text, crop fractions, and either a written asset path
or base64 blob; flag `missing_embedded_image` when the blob is absent.
- **chart** — record `chart_type`, optional title, categories, and series values.
- **line / connector** — derive endpoints from the bbox plus flip flags, and read
arrow head/tail types from the `<a:ln>` element.
### Media & notes helpers
- **Embedded relationship ids** — iterate the shape element and collect attributes
ending in `}embed`, then resolve via `shape.part.related_part(rid)`.
- **Package media counts** — open the `.pptx` with `zipfile` and count names under
`ppt/media/` and `ppt/embeddings/`.
- **Notes** — parse each `ppt/slides/_rels/slideN.xml.rels`, follow the
`notesSlide` relationship, and join `<a:t>` text nodes.
## Safe attribute access
`python-pptx` raises on many optional properties (fills, colors, line styles).
Wrap reads in a small guard so extraction degrades gracefully:
```python
def _safe_attr(value, name):
if value is None:
return None
try:
return getattr(value, name)
except (AttributeError, TypeError, ValueError):
return None
```
Apply the same defensive pattern around `fill.type`, `line.width`, `color.rgb`,
`shape.image`, and chart/table accessors, since any of them can fail on real-world decks.