pptxizza 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pptxizza-0.1.0/PKG-INFO +197 -0
- pptxizza-0.1.0/README.md +184 -0
- pptxizza-0.1.0/pyproject.toml +31 -0
- pptxizza-0.1.0/setup.cfg +4 -0
- pptxizza-0.1.0/src/pptxizza/__init__.py +12 -0
- pptxizza-0.1.0/src/pptxizza/charts.py +95 -0
- pptxizza-0.1.0/src/pptxizza/colors.py +55 -0
- pptxizza-0.1.0/src/pptxizza/media.py +121 -0
- pptxizza-0.1.0/src/pptxizza/opc.py +248 -0
- pptxizza-0.1.0/src/pptxizza/presentation.py +202 -0
- pptxizza-0.1.0/src/pptxizza/shapes/__init__.py +12 -0
- pptxizza-0.1.0/src/pptxizza/shapes/bar_chart.py +7 -0
- pptxizza-0.1.0/src/pptxizza/shapes/base_shape.py +208 -0
- pptxizza-0.1.0/src/pptxizza/shapes/chart.py +27 -0
- pptxizza-0.1.0/src/pptxizza/shapes/circle.py +57 -0
- pptxizza-0.1.0/src/pptxizza/shapes/factory.py +84 -0
- pptxizza-0.1.0/src/pptxizza/shapes/format.py +168 -0
- pptxizza-0.1.0/src/pptxizza/shapes/graphic_frame.py +27 -0
- pptxizza-0.1.0/src/pptxizza/shapes/line_chart.py +7 -0
- pptxizza-0.1.0/src/pptxizza/shapes/picture.py +52 -0
- pptxizza-0.1.0/src/pptxizza/shapes/pie_chart.py +7 -0
- pptxizza-0.1.0/src/pptxizza/shapes/rect.py +57 -0
- pptxizza-0.1.0/src/pptxizza/shapes/shape.py +99 -0
- pptxizza-0.1.0/src/pptxizza/shapes/table.py +256 -0
- pptxizza-0.1.0/src/pptxizza/slide.py +304 -0
- pptxizza-0.1.0/src/pptxizza/templates/default.pptx +0 -0
- pptxizza-0.1.0/src/pptxizza/text.py +108 -0
- pptxizza-0.1.0/src/pptxizza/util.py +47 -0
- pptxizza-0.1.0/src/pptxizza/xml_utils.py +96 -0
- pptxizza-0.1.0/src/pptxizza.egg-info/PKG-INFO +197 -0
- pptxizza-0.1.0/src/pptxizza.egg-info/SOURCES.txt +36 -0
- pptxizza-0.1.0/src/pptxizza.egg-info/dependency_links.txt +1 -0
- pptxizza-0.1.0/src/pptxizza.egg-info/requires.txt +4 -0
- pptxizza-0.1.0/src/pptxizza.egg-info/top_level.txt +1 -0
- pptxizza-0.1.0/tests/test_image_insertion.py +37 -0
- pptxizza-0.1.0/tests/test_shapes.py +48 -0
- pptxizza-0.1.0/tests/test_slides.py +39 -0
- pptxizza-0.1.0/tests/test_table.py +113 -0
pptxizza-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pptxizza
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A streamlined, LLM-friendly pip package for creating and editing pptx files from templates.
|
|
5
|
+
Author: Kameron
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: lxml>=4.9.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
13
|
+
|
|
14
|
+
# pptxizza
|
|
15
|
+
|
|
16
|
+
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!
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **Object-Oriented Integrity**: Native interaction with shapes (`Shape`, `Rect`, `Circle`) and charts (`BarChart`, `PieChart`, etc.).
|
|
21
|
+
- **Programmatic Shape Insertion**: Generate and structure exact geometry auto-shapes or vector pictures dynamically from code.
|
|
22
|
+
- **Text Replacement**: Replace `{{mustache}}` template variables anywhere on a slide, or target specific named shapes.
|
|
23
|
+
- **Image Replacement**: Easily swap out placeholder images with `.png`, `.jpg`, or `.svg` files while preserving position and styling.
|
|
24
|
+
- **Chart & Table Data Binding**: Update the underlying data of native PowerPoint charts (`BarChart`, `PieChart`, `LineChart`) directly via their class objects.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
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:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install -e .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Dependencies:
|
|
35
|
+
- `lxml`
|
|
36
|
+
|
|
37
|
+
## Quick Start (Templating)
|
|
38
|
+
|
|
39
|
+
The most robust way to use `pptxizza` is to create a template (`template.pptx`) with some named placeholders or `{{mustache_keys}}`, then parse it:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from pptxizza import Presentation
|
|
43
|
+
|
|
44
|
+
def main():
|
|
45
|
+
pres = Presentation("template.pptx")
|
|
46
|
+
slide = pres.slides[0]
|
|
47
|
+
|
|
48
|
+
# Fill the slide with dynamic content mapped to shape names
|
|
49
|
+
slide.fill({
|
|
50
|
+
"{{title}}": "Quarterly Business Review", # Global mustache text replacement
|
|
51
|
+
"SubtitleShape": "Q3 2026 Results", # Named shape text replacement
|
|
52
|
+
"LogoPlaceholder": "company_logo.svg" # Named picture shape replacement (injects SVG!)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
pres.save("output.pptx")
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
main()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Creating a Presentation from Scratch
|
|
62
|
+
|
|
63
|
+
If you just need a blank presentation without an existing template, you can initialize `Presentation` with no arguments.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from pptxizza import Presentation
|
|
67
|
+
|
|
68
|
+
def main():
|
|
69
|
+
# Calling Presentation() without an argument loads a default blank template
|
|
70
|
+
pres = Presentation()
|
|
71
|
+
|
|
72
|
+
# You can now add slides or insert shapes programmatically
|
|
73
|
+
# ...
|
|
74
|
+
|
|
75
|
+
pres.save("new_presentation.pptx")
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
main()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## The Object-Oriented API (`pptxizza.shapes`)
|
|
82
|
+
|
|
83
|
+
`pptxizza` maps OpenXML structures directly into typed Python objects, allowing for programmatic instantiation and highly specific template discovery.
|
|
84
|
+
|
|
85
|
+
### 1. Generating & Inserting Shapes Programmatically
|
|
86
|
+
|
|
87
|
+
Instead of relying solely on existing templates, you can import granular geometric components from `pptxizza.shapes` to build slides from scratch!
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from pptxizza.shapes import Shape, Rect, Circle, Picture, Table
|
|
91
|
+
from pptxizza import Inches
|
|
92
|
+
|
|
93
|
+
slide = pres.slides[0]
|
|
94
|
+
|
|
95
|
+
# Create a primitive Rect shape with text
|
|
96
|
+
my_rect = Rect(text="Action Item", x=Inches(1), y=Inches(1), cx=Inches(3), cy=Inches(1))
|
|
97
|
+
|
|
98
|
+
# Create an Oval/Circle
|
|
99
|
+
my_circle = Circle(text="1", x=Inches(5), y=Inches(1), cx=Inches(1), cy=Inches(1))
|
|
100
|
+
|
|
101
|
+
# Create a Table (3 rows, 3 columns)
|
|
102
|
+
my_table = Table(rows=3, cols=3, x=Inches(1), y=Inches(3), cx=Inches(6), cy=Inches(1.5))
|
|
103
|
+
|
|
104
|
+
# Create an SVG Vector graphic dynamically
|
|
105
|
+
my_ufo = Picture(image_path="ufo.svg", x=Inches(3), y=Inches(5), cx=Inches(2), cy=Inches(2))
|
|
106
|
+
|
|
107
|
+
# Insert the objects natively. Background relation mapping is handled automatically!
|
|
108
|
+
slide.insert_shape(my_rect)
|
|
109
|
+
slide.insert_shape(my_circle)
|
|
110
|
+
slide.insert_shape(my_table)
|
|
111
|
+
slide.insert_shape(my_ufo)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
_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.
|
|
115
|
+
|
|
116
|
+
### 2. Inspecting and Manipulating Dynamic Shapes
|
|
117
|
+
|
|
118
|
+
`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`.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
for shape in slide.shapes:
|
|
122
|
+
print(f"Discovered: {type(shape).__name__} named '{shape.shape_name}'")
|
|
123
|
+
|
|
124
|
+
# You can access common underlying properties easily!
|
|
125
|
+
shape.x_inches += 1.5 # shift everything right by 1.5 inches
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 3. Updating Chart Data via Shape Objects
|
|
129
|
+
|
|
130
|
+
When `slide.shapes` returns a chart class (e.g. `BarChart`, `PieChart`), you can call `.replace_data()` directly onto the object to overwrite its internal points caches.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from pptxizza.shapes import BarChart
|
|
134
|
+
|
|
135
|
+
for shape in slide.shapes:
|
|
136
|
+
if isinstance(shape, BarChart) and shape.shape_name == "SalesChart":
|
|
137
|
+
categories = ["Jan", "Feb", "Mar"]
|
|
138
|
+
data = {
|
|
139
|
+
0: [150, 200, 250], # Update series 0 by its index
|
|
140
|
+
"Europe": [120, 180, 210] # Update series by string name matching
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Inject seamlessly into the cache!
|
|
144
|
+
shape.replace_data(data, categories)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 4. Manipulating Tables
|
|
148
|
+
|
|
149
|
+
`Table` objects allow for granular cell access and styling using presets.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from pptxizza.shapes import Table
|
|
153
|
+
|
|
154
|
+
for shape in slide.shapes:
|
|
155
|
+
if isinstance(shape, Table):
|
|
156
|
+
# Access cells by (row, col)
|
|
157
|
+
shape.cell(0, 0).text = "Header 1"
|
|
158
|
+
|
|
159
|
+
# Helper to set first row names
|
|
160
|
+
shape.set_column_names(["ID", "Name", "Status"])
|
|
161
|
+
|
|
162
|
+
# Add new rows or columns
|
|
163
|
+
new_row = shape.add_row()
|
|
164
|
+
new_row.cells[0].text = "001"
|
|
165
|
+
|
|
166
|
+
# Apply visual presets
|
|
167
|
+
shape.apply_preset("Medium Style 2 - Accent 1")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 5. Styling and Rich Text
|
|
171
|
+
|
|
172
|
+
Most shapes support direct styling of fill and font properties. You can also use the `Text` and `Run` objects for rich text formatting.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from pptxizza import RGBColor, HexColor, ThemeColor, Pt, Text
|
|
176
|
+
|
|
177
|
+
# Solid Fill
|
|
178
|
+
shape.fill.solid(HexColor("#4287f5"))
|
|
179
|
+
|
|
180
|
+
# Font Styling
|
|
181
|
+
shape.font.size = Pt(24)
|
|
182
|
+
shape.font.bold = True
|
|
183
|
+
shape.font.color = RGBColor(255, 255, 255)
|
|
184
|
+
|
|
185
|
+
# Rich Text with Runs
|
|
186
|
+
t = Text()
|
|
187
|
+
t.add_run("Normal text, ")
|
|
188
|
+
t.add_run("BOLD AND RED", bold=True, color=HexColor("FF0000"))
|
|
189
|
+
t.add_run(" and ", italic=True)
|
|
190
|
+
t.add_run("LARGE BLUE", size=Pt(40), color=ThemeColor("accent1"))
|
|
191
|
+
|
|
192
|
+
shape.text = t
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Advanced Native Image Support (SVG)
|
|
196
|
+
|
|
197
|
+
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!
|
pptxizza-0.1.0/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# pptxizza
|
|
2
|
+
|
|
3
|
+
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!
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Object-Oriented Integrity**: Native interaction with shapes (`Shape`, `Rect`, `Circle`) and charts (`BarChart`, `PieChart`, etc.).
|
|
8
|
+
- **Programmatic Shape Insertion**: Generate and structure exact geometry auto-shapes or vector pictures dynamically from code.
|
|
9
|
+
- **Text Replacement**: Replace `{{mustache}}` template variables anywhere on a slide, or target specific named shapes.
|
|
10
|
+
- **Image Replacement**: Easily swap out placeholder images with `.png`, `.jpg`, or `.svg` files while preserving position and styling.
|
|
11
|
+
- **Chart & Table Data Binding**: Update the underlying data of native PowerPoint charts (`BarChart`, `PieChart`, `LineChart`) directly via their class objects.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
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:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install -e .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Dependencies:
|
|
22
|
+
- `lxml`
|
|
23
|
+
|
|
24
|
+
## Quick Start (Templating)
|
|
25
|
+
|
|
26
|
+
The most robust way to use `pptxizza` is to create a template (`template.pptx`) with some named placeholders or `{{mustache_keys}}`, then parse it:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from pptxizza import Presentation
|
|
30
|
+
|
|
31
|
+
def main():
|
|
32
|
+
pres = Presentation("template.pptx")
|
|
33
|
+
slide = pres.slides[0]
|
|
34
|
+
|
|
35
|
+
# Fill the slide with dynamic content mapped to shape names
|
|
36
|
+
slide.fill({
|
|
37
|
+
"{{title}}": "Quarterly Business Review", # Global mustache text replacement
|
|
38
|
+
"SubtitleShape": "Q3 2026 Results", # Named shape text replacement
|
|
39
|
+
"LogoPlaceholder": "company_logo.svg" # Named picture shape replacement (injects SVG!)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
pres.save("output.pptx")
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
main()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Creating a Presentation from Scratch
|
|
49
|
+
|
|
50
|
+
If you just need a blank presentation without an existing template, you can initialize `Presentation` with no arguments.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from pptxizza import Presentation
|
|
54
|
+
|
|
55
|
+
def main():
|
|
56
|
+
# Calling Presentation() without an argument loads a default blank template
|
|
57
|
+
pres = Presentation()
|
|
58
|
+
|
|
59
|
+
# You can now add slides or insert shapes programmatically
|
|
60
|
+
# ...
|
|
61
|
+
|
|
62
|
+
pres.save("new_presentation.pptx")
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## The Object-Oriented API (`pptxizza.shapes`)
|
|
69
|
+
|
|
70
|
+
`pptxizza` maps OpenXML structures directly into typed Python objects, allowing for programmatic instantiation and highly specific template discovery.
|
|
71
|
+
|
|
72
|
+
### 1. Generating & Inserting Shapes Programmatically
|
|
73
|
+
|
|
74
|
+
Instead of relying solely on existing templates, you can import granular geometric components from `pptxizza.shapes` to build slides from scratch!
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from pptxizza.shapes import Shape, Rect, Circle, Picture, Table
|
|
78
|
+
from pptxizza import Inches
|
|
79
|
+
|
|
80
|
+
slide = pres.slides[0]
|
|
81
|
+
|
|
82
|
+
# Create a primitive Rect shape with text
|
|
83
|
+
my_rect = Rect(text="Action Item", x=Inches(1), y=Inches(1), cx=Inches(3), cy=Inches(1))
|
|
84
|
+
|
|
85
|
+
# Create an Oval/Circle
|
|
86
|
+
my_circle = Circle(text="1", x=Inches(5), y=Inches(1), cx=Inches(1), cy=Inches(1))
|
|
87
|
+
|
|
88
|
+
# Create a Table (3 rows, 3 columns)
|
|
89
|
+
my_table = Table(rows=3, cols=3, x=Inches(1), y=Inches(3), cx=Inches(6), cy=Inches(1.5))
|
|
90
|
+
|
|
91
|
+
# Create an SVG Vector graphic dynamically
|
|
92
|
+
my_ufo = Picture(image_path="ufo.svg", x=Inches(3), y=Inches(5), cx=Inches(2), cy=Inches(2))
|
|
93
|
+
|
|
94
|
+
# Insert the objects natively. Background relation mapping is handled automatically!
|
|
95
|
+
slide.insert_shape(my_rect)
|
|
96
|
+
slide.insert_shape(my_circle)
|
|
97
|
+
slide.insert_shape(my_table)
|
|
98
|
+
slide.insert_shape(my_ufo)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
_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.
|
|
102
|
+
|
|
103
|
+
### 2. Inspecting and Manipulating Dynamic Shapes
|
|
104
|
+
|
|
105
|
+
`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`.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
for shape in slide.shapes:
|
|
109
|
+
print(f"Discovered: {type(shape).__name__} named '{shape.shape_name}'")
|
|
110
|
+
|
|
111
|
+
# You can access common underlying properties easily!
|
|
112
|
+
shape.x_inches += 1.5 # shift everything right by 1.5 inches
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 3. Updating Chart Data via Shape Objects
|
|
116
|
+
|
|
117
|
+
When `slide.shapes` returns a chart class (e.g. `BarChart`, `PieChart`), you can call `.replace_data()` directly onto the object to overwrite its internal points caches.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from pptxizza.shapes import BarChart
|
|
121
|
+
|
|
122
|
+
for shape in slide.shapes:
|
|
123
|
+
if isinstance(shape, BarChart) and shape.shape_name == "SalesChart":
|
|
124
|
+
categories = ["Jan", "Feb", "Mar"]
|
|
125
|
+
data = {
|
|
126
|
+
0: [150, 200, 250], # Update series 0 by its index
|
|
127
|
+
"Europe": [120, 180, 210] # Update series by string name matching
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Inject seamlessly into the cache!
|
|
131
|
+
shape.replace_data(data, categories)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 4. Manipulating Tables
|
|
135
|
+
|
|
136
|
+
`Table` objects allow for granular cell access and styling using presets.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from pptxizza.shapes import Table
|
|
140
|
+
|
|
141
|
+
for shape in slide.shapes:
|
|
142
|
+
if isinstance(shape, Table):
|
|
143
|
+
# Access cells by (row, col)
|
|
144
|
+
shape.cell(0, 0).text = "Header 1"
|
|
145
|
+
|
|
146
|
+
# Helper to set first row names
|
|
147
|
+
shape.set_column_names(["ID", "Name", "Status"])
|
|
148
|
+
|
|
149
|
+
# Add new rows or columns
|
|
150
|
+
new_row = shape.add_row()
|
|
151
|
+
new_row.cells[0].text = "001"
|
|
152
|
+
|
|
153
|
+
# Apply visual presets
|
|
154
|
+
shape.apply_preset("Medium Style 2 - Accent 1")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 5. Styling and Rich Text
|
|
158
|
+
|
|
159
|
+
Most shapes support direct styling of fill and font properties. You can also use the `Text` and `Run` objects for rich text formatting.
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from pptxizza import RGBColor, HexColor, ThemeColor, Pt, Text
|
|
163
|
+
|
|
164
|
+
# Solid Fill
|
|
165
|
+
shape.fill.solid(HexColor("#4287f5"))
|
|
166
|
+
|
|
167
|
+
# Font Styling
|
|
168
|
+
shape.font.size = Pt(24)
|
|
169
|
+
shape.font.bold = True
|
|
170
|
+
shape.font.color = RGBColor(255, 255, 255)
|
|
171
|
+
|
|
172
|
+
# Rich Text with Runs
|
|
173
|
+
t = Text()
|
|
174
|
+
t.add_run("Normal text, ")
|
|
175
|
+
t.add_run("BOLD AND RED", bold=True, color=HexColor("FF0000"))
|
|
176
|
+
t.add_run(" and ", italic=True)
|
|
177
|
+
t.add_run("LARGE BLUE", size=Pt(40), color=ThemeColor("accent1"))
|
|
178
|
+
|
|
179
|
+
shape.text = t
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Advanced Native Image Support (SVG)
|
|
183
|
+
|
|
184
|
+
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!
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pptxizza"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Kameron" },
|
|
10
|
+
]
|
|
11
|
+
description = "A streamlined, LLM-friendly pip package for creating and editing pptx files from templates."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"lxml>=4.9.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
dev = [
|
|
24
|
+
"pytest>=7.0.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.package-data]
|
|
31
|
+
pptxizza = ["templates/*.pptx"]
|
pptxizza-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .presentation import Presentation
|
|
2
|
+
from .util import Length, Inches, Cm, Pt, Emu
|
|
3
|
+
from .colors import Color, RGBColor, HexColor, ThemeColor
|
|
4
|
+
from .text import Text, Run
|
|
5
|
+
from .shapes.table import Table
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Presentation",
|
|
9
|
+
"Length", "Inches", "Cm", "Pt", "Emu",
|
|
10
|
+
"Color", "RGBColor", "HexColor", "ThemeColor",
|
|
11
|
+
"Text", "Run", "Table"
|
|
12
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from typing import Dict, List, Any, Optional, Union
|
|
2
|
+
from .xml_utils import etree, xpath, qname
|
|
3
|
+
|
|
4
|
+
def replace_chart_data(slide: Any, shape_name: str, data: Dict[Union[int, str], List[Any]], categories: List[str]) -> None:
|
|
5
|
+
"""
|
|
6
|
+
Replaces the underlying data cache bindings inside an existing chart shape.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
slide (Slide): The origin slide wrapper instance resolving the chart relational context.
|
|
10
|
+
shape_name (str): The logical embedded drawingML name attribute for the chart.
|
|
11
|
+
data (Dict[Union[int, str], List[Any]]): A dictionary mapping Series index integers or string headers to lists of arbitrary values.
|
|
12
|
+
categories (List[str]): Overriding axis category label names (e.g. Month headers / "Q1", "Q2").
|
|
13
|
+
|
|
14
|
+
Raises:
|
|
15
|
+
ValueError: If the shape wasn't found, isn't a chart, or lacks valid XML relationship dependencies.
|
|
16
|
+
"""
|
|
17
|
+
cnvprs = xpath(slide._xml, f"//p:cNvPr[@name='{shape_name}']")
|
|
18
|
+
if not cnvprs:
|
|
19
|
+
raise ValueError(f"Chart shape '{shape_name}' not found on slide.")
|
|
20
|
+
|
|
21
|
+
parent = cnvprs[0].getparent()
|
|
22
|
+
if parent is None:
|
|
23
|
+
raise ValueError("Invalid shape hierarchy")
|
|
24
|
+
graphicFrame = parent.getparent()
|
|
25
|
+
if graphicFrame is None:
|
|
26
|
+
raise ValueError("Invalid shape hierarchy")
|
|
27
|
+
|
|
28
|
+
chart_ref = xpath(graphicFrame, ".//c:chart")
|
|
29
|
+
if not chart_ref:
|
|
30
|
+
raise ValueError(f"Shape '{shape_name}' is not a chart.")
|
|
31
|
+
|
|
32
|
+
rId = chart_ref[0].get(qname("r", "id"))
|
|
33
|
+
|
|
34
|
+
slide_rels = slide._package.get_rels(slide.part_name)
|
|
35
|
+
rel = slide_rels.rels.get(rId)
|
|
36
|
+
if not rel:
|
|
37
|
+
raise ValueError(f"Relationship {rId} for chart not found.")
|
|
38
|
+
|
|
39
|
+
target = rel['Target']
|
|
40
|
+
if target.startswith("../"):
|
|
41
|
+
target = target[3:]
|
|
42
|
+
chart_part_name = f"ppt/{target}"
|
|
43
|
+
|
|
44
|
+
chart_xml = slide._package.get_xml_part(chart_part_name)
|
|
45
|
+
if chart_xml is None:
|
|
46
|
+
raise ValueError(f"Chart part {chart_part_name} not found in package.")
|
|
47
|
+
|
|
48
|
+
series_nodes = xpath(chart_xml, "//c:ser")
|
|
49
|
+
for idx, ser in enumerate(series_nodes):
|
|
50
|
+
ser_vals = data.get(idx) or data.get(str(idx))
|
|
51
|
+
if ser_vals is None:
|
|
52
|
+
tx_node = xpath(ser, "c:tx//c:v")
|
|
53
|
+
if tx_node and tx_node[0].text in data:
|
|
54
|
+
ser_vals = data[tx_node[0].text]
|
|
55
|
+
|
|
56
|
+
if ser_vals is None:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
cat_caches = xpath(ser, "c:cat//c:strCache")
|
|
60
|
+
if not cat_caches:
|
|
61
|
+
cat_caches = xpath(ser, "c:cat//c:numCache")
|
|
62
|
+
|
|
63
|
+
if cat_caches:
|
|
64
|
+
_update_cache(cat_caches[0], categories)
|
|
65
|
+
|
|
66
|
+
val_caches = xpath(ser, "c:val//c:numCache")
|
|
67
|
+
if val_caches:
|
|
68
|
+
_update_cache(val_caches[0], ser_vals)
|
|
69
|
+
|
|
70
|
+
slide._package.set_xml_part(chart_part_name, chart_xml)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _update_cache(cache_node: etree._Element, items: List[Any]) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Overwrites the `ptCount` size cache and constructs identical `pt` mapping points internally.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
cache_node (etree._Element): The internal c:strCache or c:numCache DOM node target wrapper.
|
|
79
|
+
items (List[Any]): Array of primitive values to serialize into the point cache elements.
|
|
80
|
+
"""
|
|
81
|
+
ptCount = xpath(cache_node, "c:ptCount")
|
|
82
|
+
if not ptCount:
|
|
83
|
+
ptCount_elem = etree.SubElement(cache_node, qname("c", "ptCount"))
|
|
84
|
+
else:
|
|
85
|
+
ptCount_elem = ptCount[0]
|
|
86
|
+
ptCount_elem.set("val", str(len(items)))
|
|
87
|
+
|
|
88
|
+
for pt in xpath(cache_node, "c:pt"):
|
|
89
|
+
cache_node.remove(pt)
|
|
90
|
+
|
|
91
|
+
for i, item in enumerate(items):
|
|
92
|
+
pt_elem = etree.SubElement(cache_node, qname("c", "pt"))
|
|
93
|
+
pt_elem.set("idx", str(i))
|
|
94
|
+
v_elem = etree.SubElement(pt_elem, qname("c", "v"))
|
|
95
|
+
v_elem.text = str(item)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from lxml import etree
|
|
2
|
+
from .xml_utils import qname
|
|
3
|
+
|
|
4
|
+
class Color:
|
|
5
|
+
"""
|
|
6
|
+
Base class for shape and text colors in pptxizza.
|
|
7
|
+
Subclasses must implement _get_color_node.
|
|
8
|
+
"""
|
|
9
|
+
def _get_color_node(self) -> etree._Element:
|
|
10
|
+
raise NotImplementedError("Subclasses must implement _get_color_node")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RGBColor(Color):
|
|
14
|
+
"""
|
|
15
|
+
Represents an RGB color constructed from integer red, green, and blue values (0-255).
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, r: int, g: int, b: int) -> None:
|
|
18
|
+
self.r = r
|
|
19
|
+
self.g = g
|
|
20
|
+
self.b = b
|
|
21
|
+
|
|
22
|
+
def _get_color_node(self) -> etree._Element:
|
|
23
|
+
node = etree.Element(qname("a", "srgbClr"))
|
|
24
|
+
# Render hex format without the #
|
|
25
|
+
hex_val = f"{self.r:02X}{self.g:02X}{self.b:02X}"
|
|
26
|
+
node.set("val", hex_val)
|
|
27
|
+
return node
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class HexColor(Color):
|
|
31
|
+
"""
|
|
32
|
+
Represents a color constructed from a standard hexadecimal string (e.g. '#FF0000' or 'FF0000').
|
|
33
|
+
"""
|
|
34
|
+
def __init__(self, hex_str: str) -> None:
|
|
35
|
+
if hex_str.startswith("#"):
|
|
36
|
+
hex_str = hex_str[1:]
|
|
37
|
+
self.hex_str = hex_str.upper()
|
|
38
|
+
|
|
39
|
+
def _get_color_node(self) -> etree._Element:
|
|
40
|
+
node = etree.Element(qname("a", "srgbClr"))
|
|
41
|
+
node.set("val", self.hex_str)
|
|
42
|
+
return node
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ThemeColor(Color):
|
|
46
|
+
"""
|
|
47
|
+
Represents a color tied to the presentation's theme scheme (e.g., 'accent1', 'tx1', 'bg1').
|
|
48
|
+
"""
|
|
49
|
+
def __init__(self, theme_val: str) -> None:
|
|
50
|
+
self.theme_val = theme_val
|
|
51
|
+
|
|
52
|
+
def _get_color_node(self) -> etree._Element:
|
|
53
|
+
node = etree.Element(qname("a", "schemeClr"))
|
|
54
|
+
node.set("val", self.theme_val)
|
|
55
|
+
return node
|