# Building and analyzing presentations with python-pptx (Python)

Use **python-pptx** for creating, reading, and analyzing `.pptx` files. Install: `pip install python-pptx`.

---

## Creating presentations

### Skeleton

```python
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN

prs = Presentation()
prs.slide_width  = Inches(13.333)   # 16:9 widescreen (default)
prs.slide_height = Inches(7.5)

prs.save("deck.pptx")
```

Slide size presets (width × height in inches):

| Ratio | Width | Height |
|-------|-------|--------|
| 16:9 (default) | 13.333 | 7.5 |
| 16:10 | 13.333 | 8.333 |
| 4:3 | 10 | 7.5 |

### Adding slides

```python
# Use a built-in slide layout (index varies by theme)
# Layout 0 = Title Slide, 1 = Title and Content, 6 = Blank
slide_layout = prs.slide_layouts[1]  # Title and Content
slide = prs.slides.add_slide(slide_layout)

# Access placeholders
title = slide.shapes.title
body  = slide.placeholders[1]

title.text = "Quarterly Results"
body.text  = "Key highlights for Q1 2025"
```

### Text boxes (free-form placement)

```python
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor

txBox = slide.shapes.add_textbox(Inches(1), Inches(1.5), Inches(8), Inches(1))
tf = txBox.text_frame
tf.word_wrap = True

p = tf.paragraphs[0]
run = p.add_run()
run.text = "Bold heading text"
run.font.bold = True
run.font.size = Pt(28)
run.font.color.rgb = RGBColor(0x1E, 0x3A, 0x8A)
```

### Bullet lists

```python
from pptx.util import Inches, Pt

slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = "Key Points"

tf = slide.placeholders[1].text_frame
tf.text = "First bullet point"     # sets paragraph[0]

for item in ["Second point", "Third point", "Fourth point"]:
    p = tf.add_paragraph()
    p.text = item
    p.level = 0   # 0 = top level; 1,2 = nested

# Never embed literal bullet characters (•) — use levels as above
```

### Shapes and fills

```python
from pptx.util import Inches, Pt
from pptx.enum.shapes import MSO_SHAPE_TYPE

# Rectangle
shape = slide.shapes.add_shape(
    1,  # MSO_SHAPE_TYPE.RECTANGLE = 1
    Inches(0.5), Inches(0.5), Inches(3), Inches(1.5)
)
shape.fill.solid()
shape.fill.fore_color.rgb = RGBColor(0x1E, 0x3A, 0x8A)
shape.line.color.rgb = RGBColor(0x1E, 0x3A, 0x8A)
```

### Images

```python
slide.shapes.add_picture("logo.png", Inches(0.5), Inches(0.5), width=Inches(2))
```

### Tables

```python
from pptx.util import Inches

rows, cols = 4, 3
table = slide.shapes.add_table(rows, cols, Inches(1), Inches(2), Inches(8), Inches(2.5)).table

# Header
table.cell(0, 0).text = "Region"
table.cell(0, 1).text = "Revenue"
table.cell(0, 2).text = "Margin"

# Data
data = [("North", "$120k", "33%"), ("South", "$95k", "26%"), ("West", "$140k", "36%")]
for r, (region, rev, margin) in enumerate(data, start=1):
    table.cell(r, 0).text = region
    table.cell(r, 1).text = rev
    table.cell(r, 2).text = margin
```

### Charts

```python
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
from pptx.util import Inches, Pt

# Bar / column chart
chart_data = CategoryChartData()
chart_data.categories = ["Q1", "Q2", "Q3", "Q4"]
chart_data.add_series("Revenue", (120, 95, 140, 160))
chart_data.add_series("Profit",  (40, 30, 55, 62))

chart = slide.shapes.add_chart(
    XL_CHART_TYPE.COLUMN_CLUSTERED,
    Inches(1), Inches(2), Inches(8), Inches(4.5),
    chart_data,
).chart
chart.has_legend = True
chart.chart_title.has_text_frame = True
chart.chart_title.text_frame.text = "Quarterly Performance"
```

```python
# Line chart
chart_data = CategoryChartData()
chart_data.categories = ["Jan", "Feb", "Mar", "Apr"]
chart_data.add_series("Users", (1200, 1350, 1800, 2100))

chart = slide.shapes.add_chart(
    XL_CHART_TYPE.LINE_MARKERS,
    Inches(1), Inches(2), Inches(8), Inches(4.5),
    chart_data,
).chart
```

```python
# Pie chart
chart_data = CategoryChartData()
chart_data.categories = ["Desktop", "Mobile", "Tablet"]
chart_data.add_series("Traffic", (55, 35, 10))

chart = slide.shapes.add_chart(
    XL_CHART_TYPE.PIE,
    Inches(2), Inches(2), Inches(6), Inches(4.5),
    chart_data,
).chart
chart.has_legend = True

# Data labels
plot = chart.plots[0]
plot.has_data_labels = True
plot.data_labels.show_percentage = True
plot.data_labels.show_category_name = True
```

Other chart types: `XL_CHART_TYPE.BAR_CLUSTERED`, `AREA`, `DOUGHNUT`, `RADAR`, `XY_SCATTER`. For scatter/bubble use `XyChartData` or `BubbleChartData` from `pptx.chart.data`.

### Speaker notes

```python
# Add speaker notes to a slide
notes_slide = slide.notes_slide
notes_tf = notes_slide.notes_text_frame
notes_tf.text = "Key talking point for this slide."

# Add formatted notes with multiple paragraphs
p = notes_tf.add_paragraph()
p.text = "Remember to mention the Q3 growth."
```

Accessing `slide.notes_slide` auto-creates the notes slide if it doesn't exist.

### Hyperlinks

```python
# Hyperlink on a text run
tf = slide.shapes.add_textbox(Inches(1), Inches(5), Inches(4), Inches(0.5)).text_frame
run = tf.paragraphs[0].add_run()
run.text = "Visit our website"
run.hyperlink.address = "https://example.com"

# Hyperlink on a shape (click action)
shape = slide.shapes.add_shape(1, Inches(6), Inches(5), Inches(2), Inches(0.5))
shape.text = "Click me"
shape.click_action.hyperlink.address = "https://example.com"
```

### Validation after generation

`python scripts/ooxml_kit/audit_ooxml_package.py deck.pptx`

---

## Analyzing / reading presentations

### Extract all text (outline)

```python
from pptx import Presentation

prs = Presentation("deck.pptx")

for i, slide in enumerate(prs.slides, start=1):
    print(f"\n--- Slide {i} ---")
    for shape in slide.shapes:
        if shape.has_text_frame:
            for para in shape.text_frame.paragraphs:
                text = para.text.strip()
                if text:
                    indent = "  " * para.level
                    print(f"{indent}{text}")
```

### List slide titles

```python
for i, slide in enumerate(prs.slides, start=1):
    title = slide.shapes.title
    print(f"Slide {i}: {title.text if title else '(no title)'}")
```

### Extract tables from a slide

```python
for shape in slide.shapes:
    if shape.has_table:
        for row in shape.table.rows:
            print([cell.text for cell in row.cells])
```

### Count and inspect shapes

```python
for shape in slide.shapes:
    print(f"  name={shape.name!r}  type={shape.shape_type}  "
          f"has_text={shape.has_text_frame}  has_table={shape.has_table}")
```

### Extract speaker notes

```python
for i, slide in enumerate(prs.slides, start=1):
    if slide.has_notes_slide:
        notes = slide.notes_slide.notes_text_frame.text
        if notes.strip():
            print(f"Slide {i} notes: {notes}")
```

### Extract images

```python
import os

os.makedirs("extracted_images", exist_ok=True)
for i, slide in enumerate(prs.slides, start=1):
    for shape in slide.shapes:
        if shape.shape_type == 13:  # MSO_SHAPE_TYPE.PICTURE
            image = shape.image
            ext = image.content_type.split("/")[-1]
            filename = f"extracted_images/slide{i}_{shape.name}.{ext}"
            with open(filename, "wb") as f:
                f.write(image.blob)
            print(f"Saved {filename}")
```

### Slide dimensions

```python
from pptx.util import Inches
print(f"Width:  {prs.slide_width  / 914400:.2f} in")
print(f"Height: {prs.slide_height / 914400:.2f} in")
```

---

## Decision: python-pptx vs XML editing

| Scenario | Recommended |
|----------|-------------|
| Create deck from scratch | **python-pptx** (this guide) |
| Fine-tune an existing template | Unpack → edit XML (`unpacked_deck_editing.md`) |
| Extract text / analyze content | **python-pptx** (read path above) or `markitdown` |
| Reorder / delete / insert slides | `insert_slide_copy.py` + `prune_orphan_parts.py` |
