Skip to main content

A streamlined, LLM-friendly pip package for creating and editing pptx files from templates.

Project description

pptxizza

A streamlined, LLM-friendly pip package for creating and editing .pptx files dynamically. pptxizza uses direct XML manipulation under the hood (via lxml) to fill text, replace images, and update charts and tables within existing PowerPoint templates quickly and efficiently, featuring a highly-granular Object-Oriented API for direct shape manipulation!

Why pptxizza? (vs python-pptx)

This project is heavily inspired by python-pptx, which is a fantastic and comprehensive library for working with PowerPoint files. However, pptxizza was created primarily for one very important reason: to add native SVG support haha!

Native PowerPoint doesn't typically embed pure SVGs comfortably via older XML specifications, and python-pptx lacks native support for them. pptxizza solves this by utilizing modern Microsoft Open XML extension nodes to automatically parse, embed, and inject SVGs cleanly into your slides.

Features

  • Object-Oriented Integrity: Native interaction with shapes (Shape, Rect, Circle, TextBox) and charts (BarChart, PieChart, etc.).
  • Programmatic Shape Insertion: Generate and structure exact geometry auto-shapes or vector pictures dynamically from code.
  • Text Replacement: Replace {{mustache}} template variables anywhere on a slide, or target specific named shapes.
  • Image Replacement: Easily swap out placeholder images with .png, .jpg, or .svg files while preserving position and styling.
  • Chart & Table Data Binding: Update the underlying data of native PowerPoint charts (BarChart, PieChart, LineChart) directly via their class objects.

Installation

As this package is under development, you can use it locally by ensuring the package is in your sys.path or installing it in editable mode:

pip install -e .

Dependencies:

  • lxml

Quick Start (Templating)

The most robust way to use pptxizza is to create a template (template.pptx) with some named placeholders or {{mustache_keys}}, then parse it:

from pptxizza import Presentation

def main():
    pres = Presentation("template.pptx")
    slide = pres.slides[0]

    # Fill the slide with dynamic content mapped to shape names
    slide.fill({
        "{{title}}": "Quarterly Business Review",  # Global mustache text replacement
        "SubtitleShape": "Q3 2026 Results",         # Named shape text replacement
        "LogoPlaceholder": "company_logo.svg"       # Named picture shape replacement (injects SVG!)
    })

    pres.save("output.pptx")

if __name__ == "__main__":
    main()

Creating a Presentation from Scratch

If you just need a blank presentation without an existing template, you can initialize Presentation with no arguments.

from pptxizza import Presentation

def main():
    # Calling Presentation() without an argument loads a default blank template
    pres = Presentation()
    
    # You can now add slides or insert shapes programmatically
    # ...
    
    pres.save("new_presentation.pptx")

if __name__ == "__main__":
    main()

The Object-Oriented API (pptxizza.shapes)

pptxizza maps OpenXML structures directly into typed Python objects, allowing for programmatic instantiation and highly specific template discovery.

1. Generating & Inserting Shapes Programmatically

Instead of relying solely on existing templates, you can import granular geometric components from pptxizza.shapes to build slides from scratch!

from pptxizza.shapes import Shape, Rect, Circle, Picture, Table, TextBox
from pptxizza import Inches

slide = pres.slides[0]

# Create a primitive Rect shape with text
my_rect = Rect(text="Action Item", x=Inches(1), y=Inches(1), cx=Inches(3), cy=Inches(1))

# Create an Oval/Circle
my_circle = Circle(text="1", x=Inches(5), y=Inches(1), cx=Inches(1), cy=Inches(1))

# Create a Table (3 rows, 3 columns)
my_table = Table(rows=3, cols=3, x=Inches(1), y=Inches(3), cx=Inches(6), cy=Inches(1.5))

# Create a TextBox (supports either a string or a rich Text object!)
t = Text()
t.add_run("Dynamic ", bold=True)
t.add_run("Note", color=RGBColor(255, 0, 0))
my_textbox = TextBox(text=t, x=Inches(1), y=Inches(2.5), cx=Inches(2), cy=Inches(0.5))

# Create an SVG Vector graphic dynamically
my_ufo = Picture(image_path="ufo.svg", x=Inches(3), y=Inches(5), cx=Inches(2), cy=Inches(2))

# Insert the objects natively. Background relation mapping is handled automatically!
slide.insert_shape(my_rect)
slide.insert_shape(my_circle)
slide.insert_shape(my_table)
slide.insert_shape(my_textbox)
slide.insert_shape(my_ufo)

Note: x, y, cx, and cy are tracked in English Metric Units (EMUs). All shapes also expose x_inches, y_inches, width_inches, and height_inches for easier visual math.

2. Inspecting and Manipulating Dynamic Shapes

pptxizza intelligently probes into the ZIP packages under the hood to detect exactly what's sitting on a template slide. When examining slide.shapes, generic graphic frames are dynamically resolved into explicit subclasses like BarChart, PieChart, or LineChart.

for shape in slide.shapes:
    print(f"Discovered: {type(shape).__name__} named '{shape.shape_name}'")
    
    # You can access common underlying properties easily!
    shape.x_inches += 1.5  # shift everything right by 1.5 inches

3. Updating Chart Data via Shape Objects

When slide.shapes returns a chart class (e.g. BarChart, PieChart), you can work with chart data in a pandas-like way without adding pandas as a dependency. The most ergonomic form is the split orientation: category labels in index, series names in columns, and row-major values in data.

from pptxizza.shapes import BarChart, Chart

for shape in slide.shapes:
    if isinstance(shape, BarChart) and shape.shape_name == "SalesChart":
        existing = shape.get_data(orient="split")
        print(existing)
        # {
        #     "index": ["Jan", "Feb", "Mar"],
        #     "columns": ["Series 1", "Europe"],
        #     "data": [
        #         [10, 40],
        #         [20, 50],
        #         [30, 60],
        #     ],
        # }

        # Overwrite the chart completely using a pandas-like split structure.
        # This can shrink or grow the number of series compared to the template.
        shape.set_data(
            {
                "index": ["Q1", "Q2", "Q3"],
                "columns": ["Revenue", "Europe", "APAC"],
                "data": [
                    [150, 120, 100],
                    [200, 180, 140],
                    [250, 210, 175],
                ],
            }
        )

        # A shorthand row mapping is also supported when you want category names
        # with one value per current series in each row.
        shape.set_data(
            {
                "Category 1": [1, 2, 3],
                "Category 2": [4, 5, 6],
                "Category 3": [7, 8, 9],
            }
        )

        # Patch selected series only, matched by index or existing series name.
        shape.replace_data(
            {
                0: [155, 205, 255],
                "Europe": [125, 185, 215],
            },
            ["Q1", "Q2", "Q3"],
        )
        
    if isinstance(shape, Chart):
        # Chart title support
        if not shape.has_title:
            shape.set_title("Quarterly Report")
        else:
            shape.title_text = "Updated Title"

Notes:

  • .get_data(orient="split") returns a pandas-like mapping with index, columns, and data.
  • .get_data(orient="series") returns the verbose structure with categories and series if you prefer that orientation.
  • .set_data() accepts:
    • the verbose {"categories": ..., "series": ...} structure
    • the pandas-like split structure
    • a DataFrame-like object exposing index, columns, and values.tolist() or to_numpy().tolist()
    • the shorthand row mapping {category_name: [series values...]}
  • .set_data() can shrink or grow the number of series compared to the template chart.
  • .replace_data() updates only the series you specify, matched by zero-based index or existing series name.
  • .replace_chart_data() is a compatibility alias for .replace_data().
  • Embedded Excel chart workbooks are updated alongside the chart XML, so saved presentations reflect the new data in PowerPoint.
  • If you provide unmatched series names, pptxizza raises a ValueError listing the available keys instead of silently doing nothing.

4. Formatting Chart Series

For common chart formatting tasks, you can work with chart.series or look up a single series by index or name.

from pptxizza.shapes import Chart

for shape in slide.shapes:
    if isinstance(shape, Chart):
        revenue = shape.get_series("Revenue")
        revenue.set_color("#FF6600")
        revenue.set_data_labels(
            show_value=True,
            show_category_name=True,
            position="outEnd",
            separator=" | ",
        )
        revenue.set_trendline(
            trendline_type="linear",
            display_equation=True,
            display_r_squared=True,
        )

        # Access by zero-based index as well
        shape.get_series(1).set_color((0, 120, 215))

        # Remove formatting when needed
        revenue.clear_data_labels()
        revenue.clear_trendline()

Available series helpers:

  • .set_color(color)
  • .set_data_labels(...)
  • .clear_data_labels()
  • .set_trendline(...)
  • .clear_trendline()

5. Manipulating Tables

Table objects allow for granular cell access and automatic data binding.

from pptxizza.shapes import Table

for shape in slide.shapes:
    if isinstance(shape, Table):
        # Access dimensions
        print(f"Table is {shape.row_count}x{shape.column_count}")
        
        # Populate from a list of dicts (auto-headers)
        data = [
            {"ID": 1, "Name": "Alice", "Status": "Active"},
            {"ID": 2, "Name": "Bob", "Status": "Pending"}
        ]
        shape.set_data(data)
        
        # Or populate from a matrix (list of lists)
        # shape.set_data([["H1", "H2"], ["V1", "V2"]])
        
        # Ensure the table is big enough
        shape.ensure_size(rows=5, cols=3)
        
        # Access cells by (row, col)
        shape.cell(0, 0).text = "Updated Header"
        
        # Convert back to matrix
        matrix = shape.to_matrix()

6. Styling and Rich Text

Most shapes support direct styling of fill and font properties. You can also use the Text and TextRun objects for rich text formatting with comprehensive control over horizontal and vertical alignment, character formatting, and transparency.

Text Alignment and Positioning

from pptxizza import Text, Pt

# Horizontal Alignment
text = Text(alignment="center")  # or "left", "right", "justify"
text.add_run("Centered text")

# Vertical Alignment (within text frame)
text = Text(vertical_alignment="middle")  # or "top", "bottom"
text.add_run("Vertically centered text")

# Combined
text = Text(alignment="center", vertical_alignment="middle")
text.add_run("Centered both ways")

Rich Text with Character Formatting

from pptxizza import RGBColor, HexColor, ThemeColor, Pt, Text

# Multiple formatted runs in one paragraph
t = Text()
t.add_run("Normal text, ")
t.add_run("BOLD AND RED", bold=True, color=HexColor("FF0000"))
t.add_run(" and ", italic=True)
t.add_run("LARGE BLUE", size=Pt(40), color=ThemeColor("accent1"))
t.add_run(" with ", underline=True)
t.add_run("STRIKETHROUGH", strikethrough=True)

shape.text = t

Advanced Character Formatting

# Superscript and subscript
text = Text()
text.add_run("H")
text.add_run("2", subscript=True)  # H₂O for water
text.add_run("O")

text = Text()
text.add_run("E=mc")
text.add_run("2", superscript=True)  # E=mc²

# Transparency/Opacity (0.0 = fully transparent, 1.0 = fully opaque)
text = Text()
text.add_run("Fully opaque", transparency=1.0, color=RGBColor(255, 0, 0))
text.add_run(" | Semi-transparent ", transparency=0.5, color=RGBColor(0, 255, 0))
text.add_run("Very transparent", transparency=0.2, color=RGBColor(0, 0, 255))

# Letter spacing
text = Text()
text.add_run("Spaced out text", letter_spacing=Pt(5))

# Custom font
text = Text()
text.add_run("Helvetica font", font_name="Helvetica")

Available TextRun Properties

When creating a TextRun directly or via text.add_run():

Property Type Description
text str The actual text content
bold bool Make text bold
italic bool Make text italic
underline bool Underline the text
strikethrough bool Strike through the text
superscript bool Raise text above baseline
subscript bool Lower text below baseline
color Color/str/tuple Text color (Color object, hex string, or RGB tuple)
font_name str Font typeface (e.g., "Arial", "Helvetica")
size Length Font size (e.g., Pt(12))
transparency float Opacity from 0.0 (transparent) to 1.0 (opaque)
letter_spacing Length Space between characters

Available Text Paragraph Properties

When creating a Text object:

Property Type Values Description
alignment str "left", "center", "right", "justify" Horizontal text alignment
vertical_alignment str "top", "middle", "bottom" Vertical text alignment within frame
# Solid Fill
shape.fill.solid(HexColor("#4287f5"))

# Font Styling
shape.font.size = Pt(24)
shape.font.bold = True
shape.font.color = RGBColor(255, 255, 255)

Advanced Native Image Support (SVG)

Native PowerPoint doesn't typically embed pure SVGs comfortably via older XML specifications. pptxizza utilizes modern Microsoft Open XML extension nodes natively mapping a:svgBlip relations to automatically parse, embed, and inject SVGs interchangeably with PNGs and JPGs!

# Replace any shape (placeholder) with an image directly
slide.find_text("{{image_here}}")[0].replace_with_picture("logo.svg")

Spatial Shape Queries

pptxizza lets you query shapes by their physical position on the slide, which is useful when you need to locate shapes relative to a known coordinate or another shape (e.g. finding the label closest to a data icon).

slide.find_shapes_by_distance(x, y, ...)

Returns all shapes sorted by Euclidean distance from a point (x, y) in EMUs, closest first.

from pptxizza import Inches

# All shapes sorted by distance from the top-left corner
results = slide.find_shapes_by_distance(0, 0)

# Limit to the 3 closest shapes
results = slide.find_shapes_by_distance(Inches(3), Inches(2), limit=3)

# Filter by type
from pptxizza.shapes import Picture
results = slide.find_shapes_by_distance(Inches(3), Inches(2), shape_type=Picture)

# Or by a case-insensitive class name string
results = slide.find_shapes_by_distance(Inches(3), Inches(2), shape_type="textbox")

You can also constrain the search to shapes in a specific direction relative to the point using the direction parameter:

Value Condition
"right" shape's left edge is strictly to the right of x
"left" shape's right edge is strictly to the left of x
"below" shape's top edge is strictly below y
"above" shape's bottom edge is strictly above y
# Find the closest shape that is to the right of x=3in
results = slide.find_shapes_by_distance(Inches(3), Inches(2), direction="right")

# Combine with type filter and limit
results = slide.find_shapes_by_distance(Inches(3), Inches(2), shape_type="rect", direction="below", limit=2)

slide.find_closest_shape_to(shape, ...)

A convenience wrapper around find_shapes_by_distance that uses the center of an existing shape as the search origin. The reference shape itself is always excluded from results.

# Find the 2 shapes closest to a known picture
anchor = slide.find_shapes(shape_type=Picture)[0]
nearby = slide.find_closest_shape_to(anchor, limit=2)

# Find the nearest TextBox to the right of an anchor shape
label = slide.find_closest_shape_to(anchor, shape_type="textbox", direction="right", limit=1)

All parameters (shape_type, limit, direction) behave identically to find_shapes_by_distance. The limit is applied after the reference shape is excluded, so limit=1 always returns the single closest other shape.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pptxizza-0.1.7.tar.gz (240.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pptxizza-0.1.7-py3-none-any.whl (235.2 kB view details)

Uploaded Python 3

File details

Details for the file pptxizza-0.1.7.tar.gz.

File metadata

  • Download URL: pptxizza-0.1.7.tar.gz
  • Upload date:
  • Size: 240.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for pptxizza-0.1.7.tar.gz
Algorithm Hash digest
SHA256 f4cd386b9d2be4f0cbfb23b9acd0b62f3ccf96e8df861822ceded6ec7a5b0feb
MD5 a575c95d9cc6ceea1d04d932c5aae494
BLAKE2b-256 f6afadb72234e617763e527e45f3b71d6728988a8a17a96e5415f6b941718abd

See more details on using hashes here.

File details

Details for the file pptxizza-0.1.7-py3-none-any.whl.

File metadata

  • Download URL: pptxizza-0.1.7-py3-none-any.whl
  • Upload date:
  • Size: 235.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for pptxizza-0.1.7-py3-none-any.whl
Algorithm Hash digest
SHA256 e141ac7639d2f86ed413b1edfeeda157c0acf2c226ed157d3cd31dc7f7c9efb9
MD5 8761b9bbacbb68b58d6ac94b2058e6cb
BLAKE2b-256 598aae827d0098b71da24d21d99334f0df944c0a8116ef7398bb2db84c0dcff0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page