# 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:`. - **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 `` 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 `` 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.