skills/pptx-render/SKILL.md
Use when the user asks to "render pptx", "show pptx slide", "compare with pptx", "pptx to image", "export pptx slide", "original slide", "show me the original", "what does the pptx look like", or needs to extract a specific PPTX slide's content for visual comparison.
npx skillsauth add edwinhu/workflows pptx-renderInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Announce: "I'm using pptx-render to extract PPTX slide content."
Extracts content from PPTX slides using python-pptx. Primary use case: understanding what a PPTX slide contains (shapes, text, positions, images) for comparison against Typst slides, especially diagrams and visual items (VIS-* in content inventories).
| Tool | Source |
|------|--------|
| python-pptx | pixi project dependency |
If the user references a content inventory item (e.g., VIS-3, DQ-7), look up its PPTX slide number:
grep "VIS-3\|the-item-id" inventory/content-inventory-XX.md
from pptx import Presentation
import json
prs = Presentation('path/to/slides.pptx')
slide = prs.slides[SLIDE_NUM - 1] # 0-indexed
for shape in slide.shapes:
info = {
'name': shape.name,
'left_in': round(shape.left / 914400, 2),
'top_in': round(shape.top / 914400, 2),
'width_in': round(shape.width / 914400, 2),
'height_in': round(shape.height / 914400, 2),
}
if shape.has_text_frame:
info['text'] = shape.text_frame.text
if shape.shape_type == 13: # MSO_SHAPE_TYPE.PICTURE
info['is_image'] = True
if shape.has_table:
info['is_table'] = True
info['rows'] = len(shape.table.rows)
info['cols'] = len(shape.table.columns)
print(json.dumps(info))
To save embedded images from a slide:
from pptx import Presentation
from pptx.enum.shapes import MSO_SHAPE_TYPE
prs = Presentation('path/to/slides.pptx')
slide = prs.slides[SLIDE_NUM - 1]
for i, shape in enumerate(slide.shapes):
if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
image = shape.image
ext = image.content_type.split('/')[-1]
with open(f'/tmp/pptx-slide-{SLIDE_NUM}-img-{i}.{ext}', 'wb') as f:
f.write(image.blob)
print(f'Saved image {i}: {image.content_type} ({shape.width/914400:.1f}x{shape.height/914400:.1f} in)')
Shape positions use inches from top-left corner:
left_in / top_in: position of shape's top-left corneris_image: true and generic names ("Picture 5") are usually clipart.shapes on groups| Shape Pattern | Likely Content |
|--------------|----------------|
| Multiple text boxes + arrows/lines at specific positions | Substantive diagram — reproduce in Typst |
| Single large Picture shape filling the slide | Clipart/stock photo — skip or replace |
| Table shape | Data table — reproduce as Typst #table |
| Text boxes only, no connectors | Text slide — no diagram needed |
| Group shapes with AutoShapes inside | Flow diagram — extract sub-shapes |
# One-liner to dump all shapes from slide N
uv run python3 -c "
from pptx import Presentation; import json
prs = Presentation('PPTX_PATH')
for s in prs.slides[N-1].shapes:
d = {'name': s.name, 'text': s.text_frame.text if s.has_text_frame else None,
'pos': f'{s.left/914400:.1f},{s.top/914400:.1f}',
'size': f'{s.width/914400:.1f}x{s.height/914400:.1f}'}
print(json.dumps(d))
"
For actual rasterization (not content extraction), use the shared x2t wrapper — ONLYOFFICE x2t is stateless and parallel-safe, unlike soffice:
# pptx -> PDF (all slides, then split with pdftoppm if per-slide PNGs needed)
python3 ${CLAUDE_SKILL_DIR}/../../scripts/doc_render.py deck.pptx deck.pdf
# pptx -> PNG (first slide only)
python3 ${CLAUDE_SKILL_DIR}/../../scripts/doc_render.py deck.pptx slide1.png
Do NOT call soffice --headless directly — it silently fails on macOS (returns 0, no output) due to profile lock issues. The wrapper prefers x2t and only falls back to soffice where x2t is absent.
development
Build the meeting-level proxy-voting × ownership panel on the WRDS SGE grid — ISS N-PX fund votes reduced to (item × block) direction cells, joined to institutional and mutual-fund ownership. Use when working with risk.voteanalysis_npx, N-PX fund-level votes, ISS→CRSP fund linking, index/passive/active voting blocks, or a proxy-voting panel that needs ownership attached.
development
Use when "CRSP CIZ", "CRSP v2", "CRSP flat file format 2.0", "crsp.dsf_v2 / msf_v2", "StkDlySecurityData", "StkMthSecurityData", "StkSecurityInfoHist", "stocknames_v2", "DlyRet / MthRet / DlyPrc / MthPrc", "SHRCD or EXCHCD equivalent in new CRSP", "SIZ to CIZ migration", "CRSP data after 2024", "CRSP delisting returns", "CRSP cumulative adjustment factors", "CRSP index INDNO / INDFAM", or any CRSP stock/index query where the legacy SIZ column names no longer exist.
development
Use when linking or deduping datasets by entity name rather than a shared key — 'fuzzy match', 'fuzzy name matching', 'entity resolution', 'record linkage', 'match company/person names', 'dedupe entity names', 'name-based join', 'bridge identifiers' (CIK ↔ permno ↔ gvkey ↔ wficn ↔ EIN ↔ personid), or any use of char n-gram TF-IDF, cosine similarity on names, `sparse_dot_topn`, or RapidFuzz at scale.
development
Use when building a publication-quality table in Python — 'regression table', 'results table', 'summary statistics table', 'etable', 'coefplot', 'great_tables', 'GT', 'gt table', 'format a table for the paper', 'export table to LaTeX/HTML', significance stars, spanners, or column formatting for a table headed into a paper, slide deck, or notebook.