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
335 lines
16 KiB
Python
335 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Advanced PPT textbox typography — per-paragraph and per-run overrides that the
|
|
basic example didn't reach. Each slide demonstrates a different scope:
|
|
slide 1 — per-paragraph align / lineSpacing override (shape default vs paragraph)
|
|
slide 2 — paragraph indents (indent / marginLeft / marginRight) for hanging-indent style
|
|
slide 3 — per-paragraph styling (bold / italic / color / size / lang) without runs
|
|
slide 4 — per-run typography (font / size / spacing / kern / lang) inside one paragraph
|
|
slide 5 — subscript / superscript convenience aliases vs canonical baseline=
|
|
slide 6 — name / zorder / autoFit / direction / font.cs (textbox-specific)
|
|
|
|
SDK twin of textboxes-advanced.sh (officecli CLI). Both produce an equivalent
|
|
textboxes-advanced.pptx. This one drives the **officecli Python SDK**
|
|
(`pip install officecli-sdk`): one resident is started and every slide, shape,
|
|
paragraph and run is shipped over the named pipe in a single `doc.batch(...)`
|
|
round-trip. Each item is the same `{"command","parent","type","props"}` dict
|
|
you'd put in an `officecli batch` list.
|
|
|
|
Usage:
|
|
pip install officecli-sdk # plus the `officecli` binary on PATH
|
|
python3 textboxes-advanced.py
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
# --- locate the SDK: prefer an installed `officecli-sdk`, else the in-repo copy
|
|
try:
|
|
import officecli # pip install officecli-sdk
|
|
except ImportError:
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
"..", "..", "..", "sdk", "python"))
|
|
import officecli
|
|
|
|
FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "textboxes-advanced.pptx")
|
|
|
|
LOREM = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
|
"Vivamus lacinia odio vitae vestibulum vestibulum.")
|
|
LONGTEXT = ("Vivamus lacinia odio vitae vestibulum vestibulum. "
|
|
"Sed molestie augue sit amet leo consequat posuere.")
|
|
|
|
|
|
def add(parent, type_, **props):
|
|
"""One `add` item in batch-shape."""
|
|
return {"command": "add", "parent": parent, "type": type_, "props": props}
|
|
|
|
|
|
def slide():
|
|
return {"command": "add", "parent": "/", "type": "slide", "props": {}}
|
|
|
|
|
|
print(f"Building {FILE} ...")
|
|
|
|
with officecli.create(FILE, "--force") as doc:
|
|
items = [
|
|
# =====================================================================
|
|
# Slide 1 — Per-paragraph overrides (align / lineSpacing in one textbox)
|
|
# =====================================================================
|
|
slide(),
|
|
add("/slide[1]", "textbox",
|
|
text="Per-paragraph overrides inside one textbox",
|
|
size="28", bold="true",
|
|
x="0.5in", y="0.3in", width="12in", height="0.6in"),
|
|
|
|
# One textbox; shape-level defaults are align=left, lineSpacing=1x.
|
|
# Each paragraph overrides one of them.
|
|
add("/slide[1]", "textbox",
|
|
x="0.5in", y="1.2in", width="13in", height="5.5in",
|
|
fill="F1FAEE", size="14",
|
|
text=f"[shape default: align=left, single-spaced] {LOREM}"),
|
|
add("/slide[1]/shape[2]", "paragraph",
|
|
text=f"[paragraph override: align=center] {LOREM}", align="center"),
|
|
add("/slide[1]/shape[2]", "paragraph",
|
|
text=f"[paragraph override: align=right] {LOREM}", align="right"),
|
|
add("/slide[1]/shape[2]", "paragraph",
|
|
text=f"[paragraph override: align=justify + lineSpacing=2x] {LOREM} {LOREM}",
|
|
align="justify", lineSpacing="2x"),
|
|
add("/slide[1]/shape[2]", "paragraph",
|
|
text=f"[paragraph override: lineSpacing=18pt fixed] {LOREM}",
|
|
lineSpacing="18pt"),
|
|
|
|
# =====================================================================
|
|
# Slide 2 — Paragraph indents (indent / marginLeft / marginRight)
|
|
# =====================================================================
|
|
slide(),
|
|
add("/slide[2]", "textbox",
|
|
text="Paragraph indents — indent / marginLeft / marginRight",
|
|
size="28", bold="true",
|
|
x="0.5in", y="0.3in", width="12in", height="0.6in"),
|
|
|
|
# Reference (no indent)
|
|
add("/slide[2]", "textbox",
|
|
x="0.5in", y="1.3in", width="13in", height="1in",
|
|
fill="F1FAEE", size="14",
|
|
text=f"[default: no indent] {LOREM} {LOREM}"),
|
|
# Left indent (whole paragraph shifted right)
|
|
add("/slide[2]", "textbox",
|
|
x="0.5in", y="2.5in", width="13in", height="1in",
|
|
fill="A8DADC", size="14",
|
|
text=f"[marginLeft=1in] {LOREM} {LOREM}",
|
|
marginLeft="1in"),
|
|
# First-line indent (only first line is shifted; rest flush left)
|
|
add("/slide[2]", "textbox",
|
|
x="0.5in", y="3.7in", width="13in", height="1in",
|
|
fill="F4A261", size="14",
|
|
text=f"[indent=0.5in first-line] {LOREM} {LOREM}",
|
|
indent="0.5in"),
|
|
# Hanging indent (negative indent + positive marginLeft pulls first line back left)
|
|
add("/slide[2]", "textbox",
|
|
x="0.5in", y="4.9in", width="13in", height="1in",
|
|
fill="A8DADC", size="14",
|
|
text=f"[hanging: marginLeft=0.6in + indent=-0.5in] {LOREM} {LOREM}",
|
|
marginLeft="0.6in", indent="-0.5in"),
|
|
# Right margin (text narrowed from the right)
|
|
add("/slide[2]", "textbox",
|
|
x="0.5in", y="6.1in", width="13in", height="1in",
|
|
fill="F4A261", size="14",
|
|
text=f"[marginRight=2in] {LOREM} {LOREM}",
|
|
marginRight="2in"),
|
|
|
|
# =====================================================================
|
|
# Slide 3 — Per-paragraph styling (bold / italic / color / size / lang)
|
|
# =====================================================================
|
|
slide(),
|
|
add("/slide[3]", "textbox",
|
|
text="Per-paragraph styling (no runs needed)",
|
|
size="28", bold="true",
|
|
x="0.5in", y="0.3in", width="12in", height="0.6in"),
|
|
|
|
# One textbox; each paragraph carries its own bold/italic/color/size/lang.
|
|
add("/slide[3]", "textbox",
|
|
x="0.5in", y="1.2in", width="13in", height="5in",
|
|
fill="F1FAEE", size="14",
|
|
text="[shape default: 14pt black] Default paragraph styling."),
|
|
add("/slide[3]/shape[2]", "paragraph",
|
|
text="[bold=true at paragraph level] Whole paragraph is bold.",
|
|
bold="true"),
|
|
add("/slide[3]/shape[2]", "paragraph",
|
|
text="[italic=true at paragraph level] Whole paragraph is italic.",
|
|
italic="true"),
|
|
add("/slide[3]/shape[2]", "paragraph",
|
|
text="[color=E63946 at paragraph level] Whole paragraph is red.",
|
|
color="E63946"),
|
|
add("/slide[3]/shape[2]", "paragraph",
|
|
text="[size=22 at paragraph level] Whole paragraph is 22pt.",
|
|
size="22"),
|
|
add("/slide[3]/shape[2]", "paragraph",
|
|
text="[lang=fr-FR at paragraph level] Lorem ipsum dolor sit amet.",
|
|
lang="fr-FR", color="2A9D8F"),
|
|
|
|
# =====================================================================
|
|
# Slide 4 — Per-run typography (font / size / spacing / kern / lang)
|
|
# =====================================================================
|
|
slide(),
|
|
add("/slide[4]", "textbox",
|
|
text="Per-run typography in one paragraph",
|
|
size="28", bold="true",
|
|
x="0.5in", y="0.3in", width="12in", height="0.6in"),
|
|
|
|
# Empty textbox we'll build run-by-run
|
|
add("/slide[4]", "textbox",
|
|
x="0.5in", y="1.5in", width="13in", height="1in",
|
|
text="", size="20"),
|
|
add("/slide[4]/shape[1]/p[1]", "run", text="Mix "),
|
|
add("/slide[4]/shape[1]/p[1]", "run",
|
|
text="Times ", font="Times New Roman", size="24"),
|
|
add("/slide[4]/shape[1]/p[1]", "run",
|
|
text="Courier ", font="Courier New", size="18"),
|
|
add("/slide[4]/shape[1]/p[1]", "run",
|
|
text="Georgia", font="Georgia", size="28", bold="true"),
|
|
|
|
# Per-run character spacing
|
|
add("/slide[4]", "textbox",
|
|
x="0.5in", y="3in", width="13in", height="1in",
|
|
text="", size="20", bold="true"),
|
|
add("/slide[4]/shape[2]/p[1]", "run", text="Normal "),
|
|
add("/slide[4]/shape[2]/p[1]", "run",
|
|
text="TIGHTENED ", spacing="-1", color="E63946"),
|
|
add("/slide[4]/shape[2]/p[1]", "run",
|
|
text="LOOSENED ", spacing="4", color="2A9D8F"),
|
|
add("/slide[4]/shape[2]/p[1]", "run",
|
|
text="EXPANDED", spacing="8", color="1D3557"),
|
|
|
|
# Per-run kerning threshold
|
|
add("/slide[4]", "textbox",
|
|
x="0.5in", y="4.3in", width="13in", height="1in",
|
|
text="", size="20", bold="true"),
|
|
add("/slide[4]/shape[3]/p[1]", "run",
|
|
text="AV AT WA — kern=0 ", kern="0"),
|
|
add("/slide[4]/shape[3]/p[1]", "run",
|
|
text="AV AT WA — kern=1", kern="1", color="E63946"),
|
|
|
|
# Per-run lang tag (drives spellcheck per-run)
|
|
add("/slide[4]", "textbox",
|
|
x="0.5in", y="5.6in", width="13in", height="1in",
|
|
text="", size="20"),
|
|
add("/slide[4]/shape[4]/p[1]", "run",
|
|
text="English: color ", lang="en-US"),
|
|
add("/slide[4]/shape[4]/p[1]", "run",
|
|
text="British: colour ", lang="en-GB", color="2A9D8F"),
|
|
add("/slide[4]/shape[4]/p[1]", "run",
|
|
text="Français: couleur", lang="fr-FR", color="E63946"),
|
|
|
|
# =====================================================================
|
|
# Slide 5 — subscript / superscript aliases vs canonical baseline=
|
|
# =====================================================================
|
|
slide(),
|
|
add("/slide[5]", "textbox",
|
|
text="subscript / superscript aliases",
|
|
size="28", bold="true",
|
|
x="0.5in", y="0.3in", width="12in", height="0.6in"),
|
|
|
|
# Convenience form: subscript=true and superscript=true
|
|
add("/slide[5]", "textbox",
|
|
x="0.5in", y="1.5in", width="13in", height="1in",
|
|
text="", size="24"),
|
|
add("/slide[5]/shape[1]/p[1]", "run", text="H"),
|
|
add("/slide[5]/shape[1]/p[1]", "run", text="2", subscript="true"),
|
|
add("/slide[5]/shape[1]/p[1]", "run", text="SO"),
|
|
add("/slide[5]/shape[1]/p[1]", "run", text="4", subscript="true"),
|
|
add("/slide[5]/shape[1]/p[1]", "run", text=" x"),
|
|
add("/slide[5]/shape[1]/p[1]", "run", text="2", superscript="true"),
|
|
add("/slide[5]/shape[1]/p[1]", "run", text=" + y"),
|
|
add("/slide[5]/shape[1]/p[1]", "run", text="2", superscript="true"),
|
|
add("/slide[5]/shape[1]/p[1]", "run", text=" = r"),
|
|
add("/slide[5]/shape[1]/p[1]", "run", text="2", superscript="true"),
|
|
|
|
add("/slide[5]", "textbox",
|
|
text="subscript=true ≡ baseline=sub superscript=true ≡ baseline=super",
|
|
size="14", italic="true", color="666666",
|
|
x="0.5in", y="2.8in", width="13in", height="0.5in"),
|
|
|
|
# Custom baseline percent — neither alias gives you this
|
|
add("/slide[5]", "textbox",
|
|
x="0.5in", y="3.7in", width="13in", height="1in",
|
|
text="", size="24"),
|
|
add("/slide[5]/shape[3]/p[1]", "run", text="Custom: "),
|
|
add("/slide[5]/shape[3]/p[1]", "run",
|
|
text="50%", baseline="50", color="E63946"),
|
|
add("/slide[5]/shape[3]/p[1]", "run", text=" higher / "),
|
|
add("/slide[5]/shape[3]/p[1]", "run",
|
|
text="-40%", baseline="-40", color="2A9D8F"),
|
|
add("/slide[5]/shape[3]/p[1]", "run", text=" lower"),
|
|
|
|
add("/slide[5]", "textbox",
|
|
text=("baseline= accepts signed integer percent (super≡+30, sub≡-25 by "
|
|
"convention). Custom values give arbitrary vertical offset."),
|
|
size="14", italic="true", color="666666",
|
|
x="0.5in", y="5in", width="13in", height="0.6in"),
|
|
|
|
# Per-run case rendering — run Add accepts cap / allCaps / smallCaps
|
|
add("/slide[5]", "textbox",
|
|
x="0.5in", y="5.9in", width="13in", height="0.8in",
|
|
text="", size="20", bold="true"),
|
|
add("/slide[5]/shape[4]/p[1]", "run", text="default "),
|
|
add("/slide[5]/shape[4]/p[1]", "run",
|
|
text="small caps ", cap="small", color="2A9D8F"),
|
|
add("/slide[5]/shape[4]/p[1]", "run",
|
|
text="ALL CAPS", allCaps="true", color="E63946"),
|
|
|
|
add("/slide[5]", "textbox",
|
|
text="Per-run cap=small / cap=all / cap=none, plus allCaps / smallCaps boolean aliases.",
|
|
size="12", italic="true", color="666666",
|
|
x="0.5in", y="6.8in", width="13in", height="0.5in"),
|
|
|
|
# =====================================================================
|
|
# Slide 6 — name / zorder / autoFit / direction / font.cs
|
|
# =====================================================================
|
|
slide(),
|
|
add("/slide[6]", "textbox",
|
|
text="name / zorder / autoFit / direction / font.cs",
|
|
size="28", bold="true",
|
|
x="0.5in", y="0.3in", width="13in", height="0.6in"),
|
|
|
|
# name= — label the textbox so it can be re-addressed by @name later
|
|
add("/slide[6]", "textbox",
|
|
x="0.5in", y="1.2in", width="5in", height="1.5in",
|
|
fill="F1FAEE", size="16", bold="true",
|
|
text="This is intro-box.",
|
|
name="intro-box"),
|
|
add("/slide[6]", "textbox",
|
|
text='name="intro-box" → addressable as /slide[6]/shape[@name=intro-box]',
|
|
size="12", italic="true", color="666666",
|
|
x="0.5in", y="2.8in", width="5in", height="0.5in"),
|
|
|
|
# zorder= — three overlapping textboxes with explicit stack order
|
|
add("/slide[6]", "textbox",
|
|
x="6in", y="1.2in", width="3in", height="2in",
|
|
fill="4472C4", color="FFFFFF", bold="true", size="16",
|
|
text="back (zorder=1)",
|
|
name="tb-back", zorder="1"),
|
|
add("/slide[6]", "textbox",
|
|
x="7in", y="1.6in", width="3in", height="2in",
|
|
fill="E63946", color="FFFFFF", bold="true", size="16",
|
|
text="mid (zorder=2)",
|
|
name="tb-mid", zorder="2"),
|
|
add("/slide[6]", "textbox",
|
|
x="8in", y="2.0in", width="3in", height="2in",
|
|
fill="2A9D8F", color="FFFFFF", bold="true", size="16",
|
|
text="front (zorder=3)",
|
|
name="tb-front", zorder="3"),
|
|
add("/slide[6]", "textbox",
|
|
text="zorder= controls stack depth; aliases: z-order, order.",
|
|
size="12", italic="true", color="666666",
|
|
x="6in", y="4.2in", width="5in", height="0.5in"),
|
|
|
|
# autoFit= — overflow behavior for textbox (same as shape)
|
|
add("/slide[6]", "textbox",
|
|
x="0.5in", y="3.6in", width="3in", height="1.2in",
|
|
fill="FFE66D", size="16", text=LONGTEXT,
|
|
autoFit="normal"),
|
|
add("/slide[6]", "textbox",
|
|
text="autoFit=normal (shrinks text to fit)",
|
|
size="12", italic="true", color="666666",
|
|
x="0.5in", y="4.9in", width="3in", height="0.5in"),
|
|
|
|
# direction=rtl — paragraph flows right-to-left inside a textbox
|
|
add("/slide[6]", "textbox",
|
|
x="0.5in", y="5.6in", width="5in", height="1.2in",
|
|
fill="A8DADC", size="20", bold="true",
|
|
text="مرحبا بالعالم — 2026",
|
|
direction="rtl", align="right",
|
|
**{"font.cs": "Arabic Typesetting"}),
|
|
add("/slide[6]", "textbox",
|
|
text='direction=rtl + font.cs="Arabic Typesetting" (complex-script slot)',
|
|
size="12", italic="true", color="666666",
|
|
x="0.5in", y="6.9in", width="5in", height="0.5in"),
|
|
]
|
|
|
|
doc.batch(items)
|
|
print(f" added {len(items)} slides/shapes/paragraphs/runs")
|
|
doc.send({"command": "save"})
|
|
|
|
print(f"Generated: {FILE}")
|