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 | NoneBBox 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))