power-pptx

Geometry & Arrows

The v2.8 geometry layer collapses the historical seven-line styling ritual into single calls, and gives you a proper value object for rectangular regions.

BBox — the region value object

from power_pptx import Presentation, BBox
from power_pptx.util import Inches

prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[5])   # Title Only
slide.shapes.title.text = "Geometry demo"

bb = BBox.from_inches(1, 2, 8, 4)          # left, top, width, height
left, right = bb.split_h([1, 1], gap=Inches(0.2))
top, bottom  = bb.split_v([2, 1], gap=Inches(0.1))
inner        = bb.inset(all=Inches(0.2))
cells        = bb.grid(cols=3, rows=2, gap_x=Inches(0.1), gap_y=Inches(0.1))

other = bb.sub(0.25, 0.25, 0.5, 0.5)       # fractional sub-region
bb.contains(other)                         # True
bb.intersection(other)                     # BBox | None

BBox splats into every add_* call (*bb expands to left, top, width, height). Float arithmetic on lengths is coerced at the setter, so expressions like (Inches(10) - gutter) / 2 are safe.

One-call text

slide.shapes.add_text(
    bb, text="Hello",
    size_pt=24, bold=True,
    color="#0B5CFF", align="center", anchor="middle",
)

Chainable shape colours

from power_pptx.enum.shapes import MSO_SHAPE

card = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, *bb)
card.fill_hex("#FFFFFF").line_hex("#0D0D0D", weight_pt=1.25)

Real arrows

add_connector(MSO_CONNECTOR.STRAIGHT, ...) draws a bare line — no arrowhead.add_arrow produces a connector with a real arrowhead and auto-routed endpoints (mid-edge of the target shape, pulled back by inset_pt):

start = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, *left)
end   = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, *right)

slide.shapes.add_arrow(
    start=start, end=end,
    head="triangle", color="#0B5CFF",
    weight_pt=1.5, inset_pt=6,
)

More helpers

# Replace text but keep the template formatting
title_shape = slide.shapes.title
title_shape.set_text_preserving_format("New title")

# Swap a broken / sub-quality picture for native shapes,
# sized to the enclosing card rather than the picture bbox
picture = slide.shapes.add_picture("logo.png", *BBox.from_inches(6, 1, 2, 2))
container = picture.enclosing_container()

def builder(slide, bbox):
    slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, *bbox).fill_hex("#0B5CFF")

picture.replace_with(builder, padding=Inches(0.1))

# One-call cleanup
slide.tidy()            # lint + safe auto-fixes

# Find a free region for greenfield placement
free = slide.find_empty_region(min_width=Inches(2), min_height=Inches(1))