Files
wehub-resource-sync 8cb1f9f479
Publish SDK (PyPI) / publish (push) Has been cancelled
Publish SDK (npm) / publish (@aionui/officecli-sdk) (push) Has been cancelled
SDK smoke / smoke (windows-latest) (push) Has been cancelled
Publish SDK (npm) / publish (@officecli/officecli-sdk) (push) Has been cancelled
Publish SDK (npm) / publish (@officecli/sdk) (push) Has been cancelled
Publish SDK (npm) / publish (officecli-sdk) (push) Has been cancelled
SDK smoke / smoke (macos-latest) (push) Has been cancelled
SDK smoke / smoke (ubuntu-latest) (push) Has been cancelled
Skill parity / diff (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:09:29 +08:00

213 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Textbox Showcase
Demonstrates 10 complex textbox scenarios built on the `wps:wsp` WordprocessingShape model (OOXML Drawing ML). The example is **hybrid**:
- **Scenarios 1, 4, 5, 6, 7, 8, 9, 10** use the high-level `officecli add --type textbox` command — fill, border, gradient, rotation, vertical text, geometry, corner radius, shadow, no-fill/no-line, wrap, positioning and z-order are `--prop`s on one `add`, and the inner text is formatted with `set` on the `<textbox>/p[N]` paths plus extra `add … --type paragraph` calls.
- **Scenarios 2 and 3** stay on `officecli raw-set` with pre-authored XML, because they exercise surface the high-level command does not expose (per-run mixed formatting inside one paragraph, and a nested table — see the per-scenario notes below).
Three files:
- **textbox.sh** — builds the document (high-level `add` for all but 2/3, `raw-set` for 2/3).
- **textbox.py** — SDK twin; ships the same items over one `doc.batch(...)` round-trip.
- **textbox.docx** — generated output; open in Word to see each floating shape.
- **textbox.md** — this file.
## Regenerate
```bash
cd examples/word
bash textbox.sh # or: python3 textbox.py
# → textbox.docx
```
## The two insertion paths
**High-level (1/4/5/7/9).** One `add` creates the box and its first paragraph; the returned path (`/body/textbox[N]`) is then addressable for run formatting and more paragraphs:
```bash
TB=$(officecli add textbox.docx /body --type textbox \
--prop text="Basic Textbox" --prop width=15cm --prop height=3.33cm \
--prop fill=E6F3FF --prop line.color=0070C0 --prop line.width=2pt \
--prop wrap=topAndBottom --prop textAnchor=top | grep -oE '/body/textbox\[[0-9]+\]')
officecli set textbox.docx "$TB/p[1]" --prop align=center --prop bold=true --prop color=0070C0 --prop size=14
officecli add textbox.docx "$TB" --type paragraph --prop text="… body text …"
```
Paragraph-level format keys are the **bare** forms (`bold`/`italic`/`color`/`size`/`align`) — each applies to every run in that paragraph. (For different formatting on different runs *within one paragraph*, use `raw-set` — see Scenario 2.)
**Raw (2/3/6/8/10).** The whole textbox paragraph is injected before the body `sectPr`:
```bash
officecli raw-set textbox.docx /document \
--xpath "//w:body/w:sectPr" \
--action insertbefore \
--xml '<w:p> ... <mc:AlternateContent> ... </mc:AlternateContent> ... </w:p>'
```
The `mc:AlternateContent` wrapper follows the OOXML spec:
- `mc:Choice Requires="wps"` — the modern `wps:wsp` WordprocessingShape (Word 2010+).
- `mc:Fallback` — optional VML fallback for older renderers.
Each `wps:wsp` element has these children:
- `wps:cNvSpPr` — marks it as a text box (`txBox="1"`).
- `wps:spPr` — geometry, fill, border, effects.
- `wps:txbx``w:txbxContent` — the actual paragraph/table content inside the box.
- `wps:bodyPr` — text body layout: rotation, vertical flow, wrap, insets, anchor.
## Scenario 1: Basic Textbox (Solid Fill + Border) — HIGH-LEVEL
A rectangle with a solid light-blue fill, a 2pt blue border, and top-and-bottom text wrapping.
Built with `add --type textbox`:
- `fill=E6F3FF` (light blue fill)
- `line.color=0070C0` + `line.width=2pt` (blue border)
- `wrap=topAndBottom` (body text flows above and below)
- `textAnchor=top`; the centred bold-blue title is `set` on `p[1]`, the body is a second `add … --type paragraph`.
**Features:** `fill`, `line.color`, `line.width`, `wrap=topAndBottom`, `textAnchor`, per-paragraph run formatting via `set` on the inner `p[N]`.
> The raw-XML original also carried a VML `mc:Fallback` for pre-2010 renderers; the high-level command does not emit one. If you need the legacy fallback, use `raw-set` (see the raw scenarios).
## Scenario 2: Multi-Paragraph Rich Text Textbox
A taller box with a dashed orange border and rich mixed-format content: bold, italic, underline, strikethrough, color, highlight, and right-aligned text — all inside `w:txbxContent`.
Key `wps:spPr` attributes:
- `a:prstDash val="dash"` on `a:ln` (dashed border)
- Multiple paragraphs with varied `w:rPr` combinations inside `w:txbxContent`
**Features:** dashed border (`a:prstDash val="dash|solid|dot|…"`), multi-paragraph textbox content, mixed run formatting inside `w:txbxContent` (bold/italic/underline/strike/color/highlight)
## Scenario 3: Textbox with Nested Table
A gray-bordered box containing a paragraph header followed by a `w:tbl` — demonstrating that `w:txbxContent` can hold any valid body content including tables.
Key structure inside `w:txbxContent`:
```xml
<w:p> ... heading ... </w:p>
<w:tbl>
<w:tblPr><w:tblStyle w:val="TableGrid"/></w:tblPr>
<w:tr> ... header cells with blue fill ... </w:tr>
<w:tr> ... data cells ... </w:tr>
</w:tbl>
```
**Features:** `w:tbl` nested inside `w:txbxContent` (full table-in-textbox), `w:tblStyle` reference, per-cell `w:shd` fill
## Scenario 4: Rotated Textbox (45 degrees + Gradient Fill) — HIGH-LEVEL
A box rotated 45° with a red-to-yellow gradient fill and centred white text.
Built with `add --type textbox`:
- `rotation=45` (degrees — the command converts to the `a:xfrm rot` 60000-per-degree units)
- `fill.gradient=FF6B6B,FFE66D` — a **comma-separated** stop list. (Note: this is *not* the `C1-C2:angle` syntax used by chart fills; the dash form is rejected here.)
- `line.color=C0392B` + `line.width=1.5pt`
- `textAnchor=center` (text centred despite rotation), `anchor.x=4.17cm` + `hRelative=column`
**Features:** `rotation`, `fill.gradient` (comma stop list), `line.color`/`line.width`, `textAnchor=center`, `anchor.x`/`hRelative`
## Scenario 5: Vertical Text Textbox — HIGH-LEVEL
A narrow tall box with East-Asian vertical text flow, where characters read top-to-bottom.
Built with `add --type textbox`:
- `textDirection=eaVert` (alias: `vert`; emits `wps:bodyPr vert="eaVert"`; other values `horz`, `vert`, `vert270`, `wordArtVert`)
- `fill=FFF0F5`, `line.color=8B0000` + `line.width=1pt`; the bold dark-red text is `set` on `p[1]`.
**Features:** `textDirection=eaVert` (vertical text orientation), `fill`, `line.*`
## Scenario 6: Rounded Rectangle Textbox + Drop Shadow — HIGH-LEVEL
A rounded rectangle with a soft outer drop shadow.
Built with `add --type textbox`:
- `geometry=roundRect` + `cornerRadius=16667` (the adjust-handle guide value; `0-100` is read as a percent ×1000, a value `>100` as a raw guide value)
- `shadow=true` — emits the standard outer drop shadow (blur 50800 / dist 38100 / dir 5400000 / black / 40% alpha). A compact `shadow=blur;dist;dir;color;alpha` form is also accepted for a custom shadow.
- `fill=E8F5E9`, `line.color=2E7D32` + `line.width=2.25pt`, `textAnchor=center`; the three paragraphs (bold-green title / body / italic-grey note) are `set` on `p[1..3]`.
**Features:** `geometry=roundRect`, `cornerRadius` (adjust handle), `shadow`, `line.width`, `textAnchor`
## Scenario 7: Side-by-Side Textboxes (Dashboard Cards) — HIGH-LEVEL
Three rounded metric cards floating side-by-side, each with `wrap=none` so they don't push body text.
Built with three `add --type textbox` calls (one per card):
- `geometry=roundRect` (card shape)
- `wrap=none` (boxes float freely)
- `hRelative=column` + `anchor.x=0cm / 5.28cm / 10.56cm` (horizontal offsets across the column)
- each card's accent title / big number / grey label are `set` on `p[1]`/`p[2]`/`p[3]`.
**Features:** `geometry=roundRect`, `wrap=none`, `anchor.x`/`hRelative` (horizontal positioning)
> **Known limitation:** the high-level `add` places each textbox in its own host paragraph, so the three cards sit at a slight *vertical stagger* rather than a single shared baseline. The raw-XML original packed all three `wp:anchor` into one paragraph for a perfectly aligned row — if you need pixel-exact co-baseline cards, use `raw-set`.
## Scenario 8: Borderless Transparent Textbox — HIGH-LEVEL
A completely invisible container — no fill, no border — so only the text shows (a watermark-style overlay).
Built with `add --type textbox`:
- `fill=none` and `line.color=none` — both sentinels emit `a:noFill` (fill and outline respectively). (`none`/`transparent` were previously rejected by the color parser, so this box needed raw-set.)
- `hRelative=column` + `anchor.x=1.39cm`, `textAnchor=center`; the single italic light-grey line is `set` on `p[1]`.
**Features:** `fill=none`, `line.color=none` (fully borderless/transparent), inner italic run formatting
## Scenario 9: Text Overflow Textbox — HIGH-LEVEL
A short fixed-height box holding six paragraphs — more text than fits — to show overflow clipping.
Built with `add --type textbox`:
- `height=1.67cm` with `autoFit` **omitted** → the box stays a fixed height and clips overflow. (Passing `autoFit=true` would emit `a:spAutoFit` and grow the box to fit instead.)
- `textAnchor=top` anchors content to the top so the overflow clips at the bottom; Line 1 is `set` bold-red, the rest are plain `add … --type paragraph` calls.
**Features:** fixed-height textbox (`autoFit` omitted), overflow clipping, `textAnchor=top`
## Scenario 10: Textbox Z-Order Stacking (behindDoc) — HIGH-LEVEL
Two overlapping boxes demonstrating Z-order, built with two `add --type textbox` calls:
- **Bottom layer:** `behindDoc=true` — sits behind the body text; `relativeHeight=251670528`.
- **Top layer:** `relativeHeight=251671552` (higher = front) + `fill.opacity=80` — a translucent (80%) fill so the bottom box shows through the overlap.
Both use `wrap=none` with `hRelative=column`/`anchor.x` (and the top box `vRelative=paragraph`/`anchor.y`) to overlap.
**Features:** `behindDoc` (push behind body text), `relativeHeight`/`zorder` (stacking order; higher = front), `fill.opacity` (translucent fill), `wrap=none` + `anchor.x`/`anchor.y` overlap
## Complete Feature Coverage
| Feature | Scenario |
|---------|---------|
| Solid fill (`a:solidFill`) | 1, 3, 7, 8, 10 |
| Gradient fill (`a:gradFill`/`a:gsLst`) | 4 |
| No fill (`a:noFill`) | 8 |
| Border width (`a:ln w`) + solid color | 1, 2, 3, 6, 7 |
| Dashed border (`a:prstDash`) | 2 |
| No border (`a:ln/a:noFill`) | 8 |
| Preset geometry: `rect` | 1, 8, 9, 10 |
| Preset geometry: `roundRect` + corner radius | 6, 7 |
| Shape rotation (`a:xfrm rot`) | 4 |
| Drop shadow (`a:outerShdw`) | 6 |
| Color transparency (`a:alpha`) | 10 |
| Vertical text (`wps:bodyPr vert="eaVert"`) | 5 |
| Body text anchor (`anchor="t"/"ctr"`) | 1, 4, 6, 9 |
| Text wrap: `wp:wrapTopAndBottom` | 1, 2, 3, 4, 5, 6 |
| Text wrap: `wp:wrapNone` (float freely) | 7, 10 (bottom) |
| Z-order: `relativeHeight`, `behindDoc` | 10 |
| Horizontal positioning: `wp:positionH/posOffset` | 7 |
| Nested table in textbox | 3 |
| Rich mixed-format content (`w:rPr` variants) | 2, 3 |
| VML fallback (`mc:Fallback` / `v:shape`) | — (raw-set only; high-level `add` does not emit one) |
| `mc:AlternateContent`/`mc:Choice Requires="wps"` | 2, 3 (raw scenarios) |
| **Build path** | high-level `add`: 1, 4, 5, 6, 7, 8, 9, 10 · `raw-set`: 2, 3 |
## Inspect the Generated File
```bash
# View the document outline (headings and textbox scenario labels)
officecli view textbox.docx outline
# Query all drawing anchors (each textbox is a wp:anchor drawing)
officecli query textbox.docx drawing
# Validate the generated file
officecli validate textbox.docx
```