Metadata-Version: 2.4
Name: pptxizza
Version: 0.1.5
Summary: A streamlined, LLM-friendly pip package for creating and editing pptx files from templates.
Author: Kameron
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: lxml>=4.9.0
Requires-Dist: openpyxl>=3.1.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"

# 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`](https://github.com/scanny/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:

```bash
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:

```python
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.

```python
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!

```python
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`.

```python
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`.

```python
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.

```python
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.

```python
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 `Run` objects for rich text formatting.

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

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

# Rich Text with Runs
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"))

shape.text = t
```

## 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!

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