spb/ultra-sharp-agent-skills Public
Ultra-Sharp Agent Skills — a research-first skill-authoring system + 72 production-ready skills for AI agents.
Python 100%
1<!--2Author: Simon-Pierre Boucher3Contact: contact@spboucher.ai4-->56# PPTX Recipes — python-pptx (+ LibreOffice)78## Contents9- Create: layouts, bullets, formatting10- Tables11- Images12- Charts13- Speaker notes14- Read / extract (all text, per-slide, notes)15- Modify (find-and-replace, delete a slide, reorder)16- Template reuse17- Convert / render18- Gotchas1920## Create: layouts, bullets, formatting2122```python23from pptx import Presentation24from pptx.util import Inches, Pt25from pptx.dml.color import RGBColor26from pptx.enum.text import PP_ALIGN2728prs = Presentation()29prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5) # 16:93031# Default template layout indexes: 0 title, 1 title+content, 5 title only, 6 blank32slide = prs.slides.add_slide(prs.slide_layouts[1])33slide.shapes.title.text = "Agenda"3435body = slide.placeholders[1].text_frame36body.text = "Overview" # first bullet, level 037for txt, lvl in [("Results", 0), ("By region", 1), ("Next steps", 0)]:38 p = body.add_paragraph()39 p.text, p.level = txt, lvl # levels give indented bullets — never type "•"4041# Run-level formatting42run = body.paragraphs[0].runs[0]43run.font.name = "Arial"44run.font.size = Pt(18)45run.font.bold = True46run.font.color.rgb = RGBColor(0x44, 0x72, 0xC4)47body.paragraphs[0].alignment = PP_ALIGN.LEFT4849prs.save("deck.pptx")50```5152## Tables5354```python55rows, cols = 3, 356# 0.5" margins on a 13.333" slide → 12.333" usable width57tbl = slide.shapes.add_table(rows, cols, Inches(0.5), Inches(2), Inches(12.333), Inches(3)).table58tbl.columns[0].width = Inches(4)59for c, h in enumerate(["Region", "Q1", "Q2"]):60 cell = tbl.cell(0, c)61 cell.text = h62 cell.text_frame.paragraphs[0].runs[0].font.bold = True63tbl.cell(1, 0).text = "East"64```6566## Images6768```python69# Size by width only — height scales to keep aspect ratio70slide.shapes.add_picture("chart.png", Inches(7), Inches(1.5), width=Inches(5.8))71```7273## Charts7475```python76from pptx.chart.data import CategoryChartData77from pptx.enum.chart import XL_CHART_TYPE7879data = CategoryChartData()80data.categories = ["East", "West"]81data.add_series("Q1", (100, 90))82data.add_series("Q2", (150, 120))83slide.shapes.add_chart(XL_CHART_TYPE.COLUMN_CLUSTERED,84 Inches(0.5), Inches(1.5), Inches(6), Inches(4.5), data)85```8687## Speaker notes8889```python90slide.notes_slide.notes_text_frame.text = "Mention the supply-chain caveat here."91```9293## Read / extract9495```python96from pptx import Presentation97prs = Presentation("deck.pptx")9899# All text, slide by slide (walks groups too)100def shape_texts(shapes):101 for sh in shapes:102 if sh.shape_type == 6: # group shape — recurse103 yield from shape_texts(sh.shapes)104 elif sh.has_text_frame:105 yield sh.text_frame.text106107for i, slide in enumerate(prs.slides, 1):108 print(f"--- slide {i}: {list(shape_texts(slide.shapes))}")109110# Tables111for slide in prs.slides:112 for sh in slide.shapes:113 if sh.has_table:114 rows = [[c.text for c in r.cells] for r in sh.table.rows]115116# Notes117notes = [s.notes_slide.notes_text_frame.text if s.has_notes_slide else "" for s in prs.slides]118```119120## Modify121122```python123# Find-and-replace preserving run formatting where possible124def replace_text(prs, old, new):125 for slide in prs.slides:126 for sh in slide.shapes:127 if not sh.has_text_frame:128 continue129 for p in sh.text_frame.paragraphs:130 for run in p.runs:131 if old in run.text:132 run.text = run.text.replace(old, new)133 if old in p.text: # spans runs — collapse to first run134 full = p.text.replace(old, new)135 for r in p.runs: r.text = ""136 if p.runs: p.runs[0].text = full137138# Delete a slide (no public API — drop the XML relationship)139def delete_slide(prs, index):140 xml_slides = prs.slides._sldIdLst141 xml_slides.remove(list(xml_slides)[index])142143# Reorder: remove and reinsert the sldId element at the target position144```145146## Template reuse147148```python149prs = Presentation("corporate-template.potx") # keeps theme, fonts, layouts150for layout in prs.slide_masters[0].slide_layouts:151 print(layout.name) # pick layouts by name, indexes vary per template152slide = prs.slides.add_slide(prs.slide_masters[0].slide_layouts[1])153prs.save("deck.pptx") # save as .pptx, not .potx154```155156## Convert / render157158```bash159soffice --headless --convert-to pdf deck.pptx # visual QA160pdftoppm -png -r 80 deck.pdf slide # slide-1.png … for inspection161soffice --headless --convert-to pptx legacy.ppt # legacy conversion162```163164## Gotchas165166- **Placeholder indexes are template-specific** — `placeholders[1]` on the default template is the body, but custom templates differ; iterate `slide.placeholders` and check `.placeholder_format.idx`/`.name`.167- **`prs.slides` has no `add_slide`-at-position, no delete, no copy** — deletion/reorder require the `_sldIdLst` XML manipulation above; copying slides between files is not supported (rebuild instead).168- **First paragraph already exists** in a text frame: set `tf.text` for it, use `add_paragraph()` only for the rest, or you get a leading empty line.169- **python-pptx cannot measure rendered text** — overflow is invisible until you render with LibreOffice; always visual-check final decks.170- **Colors:** `RGBColor(0x44, 0x72, 0xC4)` — no alpha channel; theme colors need `run.font.color.theme_color`.171- **Charts added by python-pptx embed an xlsx part** — replacing chart data later requires `chart.replace_data(new_data)`, not editing cells.172- **`.potx` templates open fine but must be saved as `.pptx`** to be presentable; saving over the `.potx` alters the user's template.173- **Group shapes hide their children** from a flat `slide.shapes` text walk — recurse into `shape_type == 6` (see reader recipe).174