# Text Formatting: TextFrame Tools

Reference for the `textframe` tool's actions — `set-text`, `get-text`, `find-text`, `replace-text`,
`set-font-size`/`get-font-size`, `set-bold`/`get-bold`, `set-font-color`/`get-font-color`,
`set-italic`/`get-italic`, `set-underline`/`get-underline`, `set-font-name`/`get-font-name`,
`set-alignment`/`get-alignment`, `set-bullet`/`get-bullet`, `set-autosize`/`get-autosize` — all
operate on a shape's text frame.

## Actions

| Tool | Action | Parameters | Notes |
|------|--------|------------|-------|
| `textframe` | `set-text` | `presentation_session_id`, `slide_index`, `shape_index`, `text` | Replaces the shape's entire text content. |
| `textframe` | `get-text` | `presentation_session_id`, `slide_index`, `shape_index` | Reads current text (`text`) — use before editing to avoid clobbering unrelated content. |
| `textframe` | `find-text` | `presentation_session_id`, `slide_index`, `shape_index`, `find_what`, optional `match_case`, `whole_words` | Returns `matchCount` and `matches` containing `start`, `length`, `text`. Does not edit. |
| `textframe` | `replace-text` | `presentation_session_id`, `slide_index`, `shape_index`, `find_what`, `replace_what`, optional `match_case`, `whole_words` | Replaces all original non-overlapping matches and returns `replacementCount`. An explicit empty replacement deletes matches. |
| `textframe` | `set-font-size` | `presentation_session_id`, `slide_index`, `shape_index`, `font_size` (points) | Applies to the shape's **entire** text range, not a substring. |
| `textframe` | `get-font-size` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `fontSize`. Fails if the value is mixed across the text range. |
| `textframe` | `set-bold` | `presentation_session_id`, `slide_index`, `shape_index`, `bold` (bool) | Applies to the entire text range. |
| `textframe` | `get-bold` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `bold`. Fails if the value is mixed across the text range. |
| `textframe` | `set-font-color` | `presentation_session_id`, `slide_index`, `shape_index`, `red`, `green`, `blue` (each 0-255) | RGB triplet, applies to the entire text range. |
| `textframe` | `get-font-color` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `red`, `green`, `blue`. Fails if the color is mixed across the text range. |
| `textframe` | `set-italic` | `presentation_session_id`, `slide_index`, `shape_index`, `italic` (bool) | Applies to the entire text range. |
| `textframe` | `get-italic` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `italic`. |
| `textframe` | `set-underline` | `presentation_session_id`, `slide_index`, `shape_index`, `underline` (bool) | Applies to the entire text range. |
| `textframe` | `get-underline` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `underline`. |
| `textframe` | `set-font-name` | `presentation_session_id`, `slide_index`, `shape_index`, `font_name` | Sets the typeface (e.g. `"Calibri"`, `"Georgia"`). No validation against installed fonts — an unrecognized name silently falls back to a substitute font in PowerPoint. |
| `textframe` | `get-font-name` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `fontName`. |
| `textframe` | `set-alignment` | `presentation_session_id`, `slide_index`, `shape_index`, `alignment` | Sets paragraph alignment. `alignment` is a `PpParagraphAlignment` name (see below). |
| `textframe` | `get-alignment` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `alignment`. Fails if paragraphs within the text range have mixed alignment. |
| `textframe` | `set-bullet` | `presentation_session_id`, `slide_index`, `shape_index`, `enabled` (bool), optional `character` (single character) | Turns bullets on/off for every paragraph in the text range. When enabling, `character` sets the bullet glyph (e.g. `"-"`, `"•"`); omit to keep the theme's default bullet. |
| `textframe` | `get-bullet` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `bulletEnabled` and `bulletCharacter` (null when bullets are off). |
| `textframe` | `set-autosize` | `presentation_session_id`, `slide_index`, `shape_index`, `auto_size` | Sets the text frame's auto-fit behavior. `auto_size` is a `PpAutoSize` name (see below). |
| `textframe` | `get-autosize` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `autoSize`. Fails if the value is mixed across multiple shapes. |

## Find and Replace Within One Shape

Select a text-bearing shape using its 1-based `slide_index` and `shape_index`.
Both actions use literal PowerPoint matching, not regex or wildcard patterns.
`match_case` and `whole_words` default to `false`; whole-word boundaries follow
PowerPoint's native rules. Empty search text is invalid, but whitespace is literal.
Missing replacement text is an error; an explicit `replace_what: ""` deletes matches.

`find-text` reports non-overlapping matches in ascending order. Each `start` is a
1-based PowerPoint character position in the original frame, with a `length` and
the original matched `text`. No match is a successful result with zero matches.

`replace-text` collects original matches before editing, then replaces ranges from
right to left. Inserted text is never searched again, including when it contains
the search term. The count includes identical replacements. It does not assign a
new string to the whole frame, preserving character formatting outside matched
ranges. Replacement styling and paragraph merging when deleting paragraph breaks
follow PowerPoint's native text editing behavior.

These actions do not traverse groups, tables, notes, other shapes, or other slides.
A shape without a text frame returns an error. Unexpected COM failures can leave
partial edits; no transactional rollback is promised. Save explicitly when done.

```text
textframe(action: "find-text", presentation_session_id: ..., slide_index: 1, shape_index: 2, find_what: "Draft", whole_words: true)
textframe(action: "replace-text", presentation_session_id: ..., slide_index: 1, shape_index: 2, find_what: "Draft", replace_what: "Final", whole_words: true)
```

CLI equivalents use `pptcli textframe find-text` / `replace-text`, `--session`,
`--slide-index`, `--shape-index`, `--find-what`, `--replace-what`, `--match-case`,
and `--whole-words`. Use live `--help` for exact option syntax.

## Paragraph Alignment Names

`alignment` for `set-alignment` must match a real `PpParagraphAlignment` enum member name exactly:
`ppAlignLeft`, `ppAlignCenter`, `ppAlignRight`, `ppAlignJustify`, `ppAlignDistribute`,
`ppAlignThaiDistribute`, `ppAlignJustifyLow`. Passing an unrecognized string returns
`success: false`.

## Auto-Fit / Auto-Size

`auto_size` for `set-autosize` must match a real `PpAutoSize` enum member name exactly:

| `auto_size` | Behavior |
|-------------|----------|
| `ppAutoSizeNone` | No auto-fit — text can overflow the shape's bounds (the default for most shapes). |
| `ppAutoSizeShapeToFitText` | The **shape grows/shrinks** to fit its text; text stays at its set font size. |
| `ppAutoSizeTextToFitShape` | The **text shrinks** (font scales down) to fit inside a fixed-size shape — PowerPoint's "Shrink text on overflow". |

Passing an unrecognized string returns `success: false`. Set this **after** `set-text` and any
font-size changes — `ppAutoSizeTextToFitShape` computes the shrink factor from whatever text is in
the frame at the moment PowerPoint next reflows it.

```
textframe(action: "set-text", presentation_session_id: ..., slide_index: ..., shape_index: ..., text: "A long paragraph that might overflow the box...")
textframe(action: "set-autosize", presentation_session_id: ..., slide_index: ..., shape_index: ..., auto_size: "ppAutoSizeTextToFitShape")
```

## Whole-Range Formatting Only

These formatting actions apply to a shape's **entire** text frame — there is no API here for
formatting a substring or a specific run of characters within one text box. If a slide needs
mixed formatting (e.g., a bold label next to plain description text), use **separate text boxes**
positioned next to each other rather than trying to mix runs inside one shape:

```
CORRECT — two text boxes for mixed emphasis
shape(action: "add-text-box", presentation_session_id: ..., slide_index: ..., left: 50, top: 100, width: 150, height: 30, text: "Revenue:")
textframe(action: "set-bold", presentation_session_id: ..., slide_index: ..., shape_index: <label shapeIndex>, bold: true)
shape(action: "add-text-box", presentation_session_id: ..., slide_index: ..., left: 210, top: 100, width: 300, height: 30, text: "$2.4M, up 12%")
```

## Bullet Lists

Use `textframe(action: "set-bullet", ...)` for native PowerPoint bullets — it applies PowerPoint's
own bullet glyph and per-paragraph indent, unlike a plain leading `-`/`•` character embedded in
text. Put each bullet item on its own line (`\n`-separated) in the `text` parameter first, then
turn bullets on for the whole text range:

```
textframe(action: "set-text", presentation_session_id: ..., slide_index: ..., shape_index: ...,
  text: "Revenue grew 24% year over year\nAPAC now the fastest-growing region\nRetention held steady at 91%")
textframe(action: "set-bullet", presentation_session_id: ..., slide_index: ..., shape_index: ..., enabled: true)
```

If a specific bullet glyph is required (e.g. a dash instead of the theme's default), pass
`character`: `textframe(action: "set-bullet", ..., enabled: true, character: "-")`.

## Font Size Guidance

| Element | Suggested `font_size` |
|---------|----------------------|
| Slide title | 28-40 |
| Subtitle | 18-24 |
| Body text | 14-18 |
| Table cell text | see `tables.md` |
| Captions / footnotes | 10-12 |

Keep to 2-3 distinct font sizes per slide; titles are typically bold, body text typically is not.

## Color Values

`set-font-color` takes three separate `byte` parameters (`red`, `green`, `blue`), each 0-255 —
**not** a hex string. Convert a hex color to decimal triplets before calling:

```
Hex "4472C4" → red=68, green=114, blue=196
textframe(action: "set-font-color", presentation_session_id: ..., slide_index: ..., shape_index: ..., red: 68, green: 114, blue: 196)
```

## Read Before You Overwrite

`set-text` replaces the whole text frame content. For a literal substring edit, use
`find-text` followed by `replace-text` to avoid rewriting unrelated character formatting.
To append text, call `get-text` first, compose the full new string yourself, and pass the
complete result to `set-text`; there is no append/insert operation.

```
textframe(action: "get-text", presentation_session_id: ..., slide_index: ..., shape_index: ...) → "Q3 Results"
textframe(action: "set-text", presentation_session_id: ..., slide_index: ..., shape_index: ..., text: "Q3 Results (Final)")
```

## Verify Text Fit

After setting text and font size, export the slide (see `export-and-verify.md`) to confirm the
text fits inside the shape's `width`/`height` without visually overflowing. Use
`set-autosize` (see "Auto-Fit / Auto-Size" above) to have PowerPoint handle overflow
automatically instead of manually tuning size — `ppAutoSizeTextToFitShape` shrinks the font to
fit a fixed box, `ppAutoSizeShapeToFitText` grows the box to fit the text. Without auto-size set
(`ppAutoSizeNone`, the default), text can overflow: shorten the text, reduce `font_size`, or grow
the shape with `shape(action: "set-size", ...)`.
