compono 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.
- compono-0.1.0/PKG-INFO +263 -0
- compono-0.1.0/README.md +249 -0
- compono-0.1.0/pyproject.toml +31 -0
- compono-0.1.0/src/compono/__init__.py +45 -0
- compono-0.1.0/src/compono/cli.py +70 -0
- compono-0.1.0/src/compono/fonts/.gitkeep +0 -0
- compono-0.1.0/src/compono/primitives.py +5 -0
- compono-0.1.0/src/compono/py.typed +0 -0
- compono-0.1.0/src/compono/render.py +649 -0
- compono-0.1.0/src/compono/resolver.py +230 -0
- compono-0.1.0/src/compono/schema.py +317 -0
- compono-0.1.0/src/compono/templates/default.yaml +26 -0
- compono-0.1.0/src/compono/validator.py +156 -0
compono-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: compono
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent-oriented, code-based PPTX generation library — Manim, but for PowerPoint.
|
|
5
|
+
Author: Shaik-Hamzah123
|
|
6
|
+
Author-email: Shaik-Hamzah123 <hamzah.shaik2003@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Dist: fonttools>=4.65.0
|
|
9
|
+
Requires-Dist: pydantic>=2.13.5
|
|
10
|
+
Requires-Dist: python-pptx>=1.0.2
|
|
11
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# compono
|
|
16
|
+
|
|
17
|
+
**Agent-oriented, code-based PPTX generation — "Manim, but for PowerPoint."**
|
|
18
|
+
|
|
19
|
+
compono lets an LLM agent (or a human) describe a slide deck as data —
|
|
20
|
+
headers, bullet text, stats, tables, charts, images, process sequences,
|
|
21
|
+
shapes — and get back a real, editable `.pptx` file. The agent never writes
|
|
22
|
+
raw `x`/`y`/`w`/`h` coordinates: a constraint-based layout resolver computes
|
|
23
|
+
every position from a small set of typed primitives.
|
|
24
|
+
|
|
25
|
+
Every rendered element is a genuine, editable native shape (`p:sp`, `p:pic`,
|
|
26
|
+
`p:graphicFrame`) — never a flattened image or embedded video. Open the
|
|
27
|
+
result in PowerPoint and drag a box around; it's a real object, not a
|
|
28
|
+
picture of one.
|
|
29
|
+
|
|
30
|
+
This file is both the human-facing README and the in-context reference an
|
|
31
|
+
agent uses to call compono correctly — see `skills/compono/SKILL.md` for the
|
|
32
|
+
packaged version of the same content.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install compono # not yet published — see CHANGELOG.md for status
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For local development, see `CONTRIBUTING.md`.
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from compono import render_deck
|
|
46
|
+
|
|
47
|
+
spec = {
|
|
48
|
+
"slides": [
|
|
49
|
+
{
|
|
50
|
+
"header": {"title": "Q3 Results", "subtitle": "Engineering team"},
|
|
51
|
+
"body": [
|
|
52
|
+
{
|
|
53
|
+
"primitive": "text",
|
|
54
|
+
"mode": "bullets",
|
|
55
|
+
"content": [
|
|
56
|
+
"Shipped the new layout resolver",
|
|
57
|
+
"Cut render time by 40%",
|
|
58
|
+
"Zero overflow bugs in production",
|
|
59
|
+
],
|
|
60
|
+
"emphasis_indices": [1],
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
report = render_deck(spec, "deck.pptx")
|
|
68
|
+
print(report.pptx_path, report.warnings)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Or from the command line:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
compono validate spec.json
|
|
75
|
+
compono render spec.json -o deck.pptx
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Core concepts
|
|
79
|
+
|
|
80
|
+
- **One entry point, two verbs.** `render_deck(spec, output_path)` and
|
|
81
|
+
`validate(spec)` are the only two functions you need. `validate` is cheap
|
|
82
|
+
— no pptx write, millisecond-scale — so an agent can iterate on a spec
|
|
83
|
+
before paying render cost.
|
|
84
|
+
- **A spec is plain data.** Either a raw `dict`/JSON (what an agent's
|
|
85
|
+
tool-calling naturally produces) or the typed builder classes
|
|
86
|
+
(`Deck`, `Header`, `Text`, ...) — both serialize to the identical shape.
|
|
87
|
+
There's no divergence between the two paths.
|
|
88
|
+
- **You never write coordinates.** Every primitive claims space in a slide;
|
|
89
|
+
the resolver (a CSS-flexbox-style directional box model) computes real
|
|
90
|
+
EMU positions. `grid` is the one primitive that does true 2D
|
|
91
|
+
row/column math.
|
|
92
|
+
- **Errors are fixes, not diagnoses.** Every validation/render failure is
|
|
93
|
+
`{slide, primitive, field, error, detail, fix}` — see
|
|
94
|
+
[Error shape](#error-shape) below.
|
|
95
|
+
- **render_deck returns a report, not just a file** —
|
|
96
|
+
`{pptx_path, manifest, warnings, actual_layout}` — so an agent can reason
|
|
97
|
+
about what happened without reopening the file.
|
|
98
|
+
|
|
99
|
+
## API reference
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from compono import (
|
|
103
|
+
render_deck, validate,
|
|
104
|
+
Deck, Slide, Header, Text, Image, Stat, Grid, Table, Sequence, Chart, Shape,
|
|
105
|
+
DeckValidationError,
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Symbol | Signature | Notes |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `render_deck` | `render_deck(spec, output_path, *, template=None) -> RenderReport` | Validates, resolves layout, writes a real `.pptx`. Raises `DeckValidationError` on any error — nothing is written on failure. |
|
|
112
|
+
| `validate` | `validate(spec, *, template=None) -> ValidationReport` | Schema + layout + text-overflow checks. No file I/O. Never raises — check `.valid`/`.errors`. |
|
|
113
|
+
| `DeckValidationError` | `exc.errors -> list[dict]` | The one exception type. Carries the structured error list below. |
|
|
114
|
+
|
|
115
|
+
A `Deck` is `{template?: str, slides: [Slide, ...]}`. A `Slide` is
|
|
116
|
+
`{header?: Header, body: [primitive, ...], notes?: str}`. `body` (and
|
|
117
|
+
`grid.items`) accept any primitive, keyed by its `"primitive"` field.
|
|
118
|
+
|
|
119
|
+
### Error shape
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"slide": 3,
|
|
124
|
+
"primitive": "grid.items[1]",
|
|
125
|
+
"field": "content",
|
|
126
|
+
"error": "overflow",
|
|
127
|
+
"detail": "Text is ~14pt too tall for the box at font size 18pt (6 lines).",
|
|
128
|
+
"fix": "Shorten the text, reduce bullet/line count, or split into two slides."
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Primitive catalog
|
|
133
|
+
|
|
134
|
+
Every primitive accepts an optional `id` (needed if another primitive
|
|
135
|
+
references it, e.g. a connector) and an optional `notes` (speaker notes).
|
|
136
|
+
|
|
137
|
+
| Primitive | Key fields | Purpose |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `header` | `title`, `subtitle?`, `eyebrow?`, `align` | Slide title region. |
|
|
140
|
+
| `text` | `mode` (paragraph/bullets), `content`, `columns?`, `emphasis_indices?` | Prose or bullet list. |
|
|
141
|
+
| `image` | `src?`, `placeholder`, `caption?`, `fit` (cover/contain) | A real picture, or a first-class placeholder — see below. |
|
|
142
|
+
| `stat` | `value`, `label`, `trend?` | A headline number with a label. |
|
|
143
|
+
| `grid` | `items`, `columns`, `direction`, `align`, `justify` | The one primitive with true 2D layout. Items can be any primitive, including nested grids. |
|
|
144
|
+
| `table` | `headers`, `rows`, `emphasis_row?`, `emphasis_col?` | Renders as a real OOXML table (`p:graphicFrame`), not an image. |
|
|
145
|
+
| `sequence` | `steps` (`{label, description?}`), `orientation` | A row/column of connected step boxes — process/timeline diagrams. |
|
|
146
|
+
| `chart` | `chart_type` (bar/line/pie), `categories`, `series` | A real, editable native chart with live data — not a picture of a chart. |
|
|
147
|
+
| `shape` | `kind` (rect/rounded_rect/oval/line/arrow/connector), `fill`, `border`, `connects?`, `text?` | Freeform shape, optionally with text inside, or a connector between two other primitives by `id`. |
|
|
148
|
+
|
|
149
|
+
Every schema field's description is written as an instruction (e.g. "Keep
|
|
150
|
+
under ~60 characters — longer titles will be shrunk by the resolver"), not
|
|
151
|
+
a bare type label — call `Header.model_json_schema()` (or any primitive
|
|
152
|
+
class) to get the full JSON Schema with these descriptions inline.
|
|
153
|
+
|
|
154
|
+
### Image placeholders
|
|
155
|
+
|
|
156
|
+
Set `"placeholder": true` (with an optional `caption`) instead of `src` when
|
|
157
|
+
you don't have a real image yet. It renders as an intentional design
|
|
158
|
+
element — dashed border, centered caption — and `render_deck`'s
|
|
159
|
+
`RenderReport.manifest` gets one entry per placeholder:
|
|
160
|
+
`{slide, primitive, rect: {x, y, w, h}, caption}`. A later pass (image
|
|
161
|
+
search/generation/human upload) can fill each reserved rect directly from
|
|
162
|
+
the manifest EMU rect — no re-layout needed, and the deck-building agent
|
|
163
|
+
itself never needs image-generation capability.
|
|
164
|
+
|
|
165
|
+
## Worked examples
|
|
166
|
+
|
|
167
|
+
### 1. Title slide
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"slides": [
|
|
172
|
+
{ "header": { "title": "2026 Roadmap", "subtitle": "Platform team", "eyebrow": "Q1 Kickoff" } }
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 2. Two-column comparison with a connector
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"slides": [{
|
|
182
|
+
"header": { "title": "Before vs. After" },
|
|
183
|
+
"body": [
|
|
184
|
+
{
|
|
185
|
+
"primitive": "grid",
|
|
186
|
+
"columns": 2,
|
|
187
|
+
"items": [
|
|
188
|
+
{ "id": "before", "primitive": "shape", "kind": "rounded_rect", "fill": "#EF4444",
|
|
189
|
+
"text": { "content": "Manual layout" } },
|
|
190
|
+
{ "id": "after", "primitive": "shape", "kind": "rounded_rect", "fill": "#10B981",
|
|
191
|
+
"text": { "content": "Resolver-computed layout" } }
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
{ "primitive": "shape", "kind": "connector", "connects": { "from_id": "before", "to_id": "after" } }
|
|
195
|
+
]
|
|
196
|
+
}]
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### 3. Stat + table + chart dashboard
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"slides": [{
|
|
205
|
+
"header": { "title": "Q3 Metrics" },
|
|
206
|
+
"body": [
|
|
207
|
+
{ "primitive": "stat", "value": "42%", "label": "YoY growth", "trend": "+12% vs Q2" },
|
|
208
|
+
{ "primitive": "table", "headers": ["Quarter", "Revenue"], "rows": [["Q1", "10"], ["Q2", "14"]] },
|
|
209
|
+
{ "primitive": "chart", "chart_type": "bar", "categories": ["Q1", "Q2"],
|
|
210
|
+
"series": [{ "name": "Revenue", "values": [10, 14] }] }
|
|
211
|
+
]
|
|
212
|
+
}]
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 4. Process sequence
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"slides": [{
|
|
221
|
+
"header": { "title": "Our Process" },
|
|
222
|
+
"body": [{
|
|
223
|
+
"primitive": "sequence",
|
|
224
|
+
"orientation": "horizontal",
|
|
225
|
+
"steps": [
|
|
226
|
+
{ "label": "Discover", "description": "Understand the problem" },
|
|
227
|
+
{ "label": "Design", "description": "Sketch options" },
|
|
228
|
+
{ "label": "Ship", "description": "Release to users" }
|
|
229
|
+
]
|
|
230
|
+
}]
|
|
231
|
+
}]
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
See `examples/minimal.json` and `examples/full_catalog.json` for complete,
|
|
236
|
+
runnable specs (also used as test fixtures).
|
|
237
|
+
|
|
238
|
+
## Fonts and overflow validation
|
|
239
|
+
|
|
240
|
+
Overflow checking (`validate`'s layout errors, and the "shrink text on
|
|
241
|
+
overflow" behavior it protects against) reads real glyph advance widths via
|
|
242
|
+
`fonttools` — no rendering required. As of this release, no font is bundled
|
|
243
|
+
into the package yet (`src/compono/fonts/` is a placeholder); validation
|
|
244
|
+
falls back to a system font if one is found (e.g. `arial.ttf` on Windows),
|
|
245
|
+
and is skipped — not faked — with a warning if none is available. A bundled,
|
|
246
|
+
OFL-licensed safe-font list is planned before the first tagged release; this
|
|
247
|
+
section will list it once shipped.
|
|
248
|
+
|
|
249
|
+
## CLI
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
compono validate spec.json
|
|
253
|
+
compono render spec.json --template fractal -o deck.pptx
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Mirrors `validate`/`render_deck` exactly — useful for agent frameworks that
|
|
257
|
+
can only shell out rather than import Python.
|
|
258
|
+
|
|
259
|
+
## Contributing
|
|
260
|
+
|
|
261
|
+
See `CONTRIBUTING.md` for dev setup, branching, and code style. If you're
|
|
262
|
+
using Claude Code, `.claude/README.md` describes the build-workflow skill,
|
|
263
|
+
review subagent, and commit/format hooks set up for this repo.
|
compono-0.1.0/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# compono
|
|
2
|
+
|
|
3
|
+
**Agent-oriented, code-based PPTX generation — "Manim, but for PowerPoint."**
|
|
4
|
+
|
|
5
|
+
compono lets an LLM agent (or a human) describe a slide deck as data —
|
|
6
|
+
headers, bullet text, stats, tables, charts, images, process sequences,
|
|
7
|
+
shapes — and get back a real, editable `.pptx` file. The agent never writes
|
|
8
|
+
raw `x`/`y`/`w`/`h` coordinates: a constraint-based layout resolver computes
|
|
9
|
+
every position from a small set of typed primitives.
|
|
10
|
+
|
|
11
|
+
Every rendered element is a genuine, editable native shape (`p:sp`, `p:pic`,
|
|
12
|
+
`p:graphicFrame`) — never a flattened image or embedded video. Open the
|
|
13
|
+
result in PowerPoint and drag a box around; it's a real object, not a
|
|
14
|
+
picture of one.
|
|
15
|
+
|
|
16
|
+
This file is both the human-facing README and the in-context reference an
|
|
17
|
+
agent uses to call compono correctly — see `skills/compono/SKILL.md` for the
|
|
18
|
+
packaged version of the same content.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install compono # not yet published — see CHANGELOG.md for status
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For local development, see `CONTRIBUTING.md`.
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from compono import render_deck
|
|
32
|
+
|
|
33
|
+
spec = {
|
|
34
|
+
"slides": [
|
|
35
|
+
{
|
|
36
|
+
"header": {"title": "Q3 Results", "subtitle": "Engineering team"},
|
|
37
|
+
"body": [
|
|
38
|
+
{
|
|
39
|
+
"primitive": "text",
|
|
40
|
+
"mode": "bullets",
|
|
41
|
+
"content": [
|
|
42
|
+
"Shipped the new layout resolver",
|
|
43
|
+
"Cut render time by 40%",
|
|
44
|
+
"Zero overflow bugs in production",
|
|
45
|
+
],
|
|
46
|
+
"emphasis_indices": [1],
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
report = render_deck(spec, "deck.pptx")
|
|
54
|
+
print(report.pptx_path, report.warnings)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or from the command line:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
compono validate spec.json
|
|
61
|
+
compono render spec.json -o deck.pptx
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Core concepts
|
|
65
|
+
|
|
66
|
+
- **One entry point, two verbs.** `render_deck(spec, output_path)` and
|
|
67
|
+
`validate(spec)` are the only two functions you need. `validate` is cheap
|
|
68
|
+
— no pptx write, millisecond-scale — so an agent can iterate on a spec
|
|
69
|
+
before paying render cost.
|
|
70
|
+
- **A spec is plain data.** Either a raw `dict`/JSON (what an agent's
|
|
71
|
+
tool-calling naturally produces) or the typed builder classes
|
|
72
|
+
(`Deck`, `Header`, `Text`, ...) — both serialize to the identical shape.
|
|
73
|
+
There's no divergence between the two paths.
|
|
74
|
+
- **You never write coordinates.** Every primitive claims space in a slide;
|
|
75
|
+
the resolver (a CSS-flexbox-style directional box model) computes real
|
|
76
|
+
EMU positions. `grid` is the one primitive that does true 2D
|
|
77
|
+
row/column math.
|
|
78
|
+
- **Errors are fixes, not diagnoses.** Every validation/render failure is
|
|
79
|
+
`{slide, primitive, field, error, detail, fix}` — see
|
|
80
|
+
[Error shape](#error-shape) below.
|
|
81
|
+
- **render_deck returns a report, not just a file** —
|
|
82
|
+
`{pptx_path, manifest, warnings, actual_layout}` — so an agent can reason
|
|
83
|
+
about what happened without reopening the file.
|
|
84
|
+
|
|
85
|
+
## API reference
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from compono import (
|
|
89
|
+
render_deck, validate,
|
|
90
|
+
Deck, Slide, Header, Text, Image, Stat, Grid, Table, Sequence, Chart, Shape,
|
|
91
|
+
DeckValidationError,
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Symbol | Signature | Notes |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `render_deck` | `render_deck(spec, output_path, *, template=None) -> RenderReport` | Validates, resolves layout, writes a real `.pptx`. Raises `DeckValidationError` on any error — nothing is written on failure. |
|
|
98
|
+
| `validate` | `validate(spec, *, template=None) -> ValidationReport` | Schema + layout + text-overflow checks. No file I/O. Never raises — check `.valid`/`.errors`. |
|
|
99
|
+
| `DeckValidationError` | `exc.errors -> list[dict]` | The one exception type. Carries the structured error list below. |
|
|
100
|
+
|
|
101
|
+
A `Deck` is `{template?: str, slides: [Slide, ...]}`. A `Slide` is
|
|
102
|
+
`{header?: Header, body: [primitive, ...], notes?: str}`. `body` (and
|
|
103
|
+
`grid.items`) accept any primitive, keyed by its `"primitive"` field.
|
|
104
|
+
|
|
105
|
+
### Error shape
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"slide": 3,
|
|
110
|
+
"primitive": "grid.items[1]",
|
|
111
|
+
"field": "content",
|
|
112
|
+
"error": "overflow",
|
|
113
|
+
"detail": "Text is ~14pt too tall for the box at font size 18pt (6 lines).",
|
|
114
|
+
"fix": "Shorten the text, reduce bullet/line count, or split into two slides."
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Primitive catalog
|
|
119
|
+
|
|
120
|
+
Every primitive accepts an optional `id` (needed if another primitive
|
|
121
|
+
references it, e.g. a connector) and an optional `notes` (speaker notes).
|
|
122
|
+
|
|
123
|
+
| Primitive | Key fields | Purpose |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `header` | `title`, `subtitle?`, `eyebrow?`, `align` | Slide title region. |
|
|
126
|
+
| `text` | `mode` (paragraph/bullets), `content`, `columns?`, `emphasis_indices?` | Prose or bullet list. |
|
|
127
|
+
| `image` | `src?`, `placeholder`, `caption?`, `fit` (cover/contain) | A real picture, or a first-class placeholder — see below. |
|
|
128
|
+
| `stat` | `value`, `label`, `trend?` | A headline number with a label. |
|
|
129
|
+
| `grid` | `items`, `columns`, `direction`, `align`, `justify` | The one primitive with true 2D layout. Items can be any primitive, including nested grids. |
|
|
130
|
+
| `table` | `headers`, `rows`, `emphasis_row?`, `emphasis_col?` | Renders as a real OOXML table (`p:graphicFrame`), not an image. |
|
|
131
|
+
| `sequence` | `steps` (`{label, description?}`), `orientation` | A row/column of connected step boxes — process/timeline diagrams. |
|
|
132
|
+
| `chart` | `chart_type` (bar/line/pie), `categories`, `series` | A real, editable native chart with live data — not a picture of a chart. |
|
|
133
|
+
| `shape` | `kind` (rect/rounded_rect/oval/line/arrow/connector), `fill`, `border`, `connects?`, `text?` | Freeform shape, optionally with text inside, or a connector between two other primitives by `id`. |
|
|
134
|
+
|
|
135
|
+
Every schema field's description is written as an instruction (e.g. "Keep
|
|
136
|
+
under ~60 characters — longer titles will be shrunk by the resolver"), not
|
|
137
|
+
a bare type label — call `Header.model_json_schema()` (or any primitive
|
|
138
|
+
class) to get the full JSON Schema with these descriptions inline.
|
|
139
|
+
|
|
140
|
+
### Image placeholders
|
|
141
|
+
|
|
142
|
+
Set `"placeholder": true` (with an optional `caption`) instead of `src` when
|
|
143
|
+
you don't have a real image yet. It renders as an intentional design
|
|
144
|
+
element — dashed border, centered caption — and `render_deck`'s
|
|
145
|
+
`RenderReport.manifest` gets one entry per placeholder:
|
|
146
|
+
`{slide, primitive, rect: {x, y, w, h}, caption}`. A later pass (image
|
|
147
|
+
search/generation/human upload) can fill each reserved rect directly from
|
|
148
|
+
the manifest EMU rect — no re-layout needed, and the deck-building agent
|
|
149
|
+
itself never needs image-generation capability.
|
|
150
|
+
|
|
151
|
+
## Worked examples
|
|
152
|
+
|
|
153
|
+
### 1. Title slide
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"slides": [
|
|
158
|
+
{ "header": { "title": "2026 Roadmap", "subtitle": "Platform team", "eyebrow": "Q1 Kickoff" } }
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 2. Two-column comparison with a connector
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"slides": [{
|
|
168
|
+
"header": { "title": "Before vs. After" },
|
|
169
|
+
"body": [
|
|
170
|
+
{
|
|
171
|
+
"primitive": "grid",
|
|
172
|
+
"columns": 2,
|
|
173
|
+
"items": [
|
|
174
|
+
{ "id": "before", "primitive": "shape", "kind": "rounded_rect", "fill": "#EF4444",
|
|
175
|
+
"text": { "content": "Manual layout" } },
|
|
176
|
+
{ "id": "after", "primitive": "shape", "kind": "rounded_rect", "fill": "#10B981",
|
|
177
|
+
"text": { "content": "Resolver-computed layout" } }
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
{ "primitive": "shape", "kind": "connector", "connects": { "from_id": "before", "to_id": "after" } }
|
|
181
|
+
]
|
|
182
|
+
}]
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 3. Stat + table + chart dashboard
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"slides": [{
|
|
191
|
+
"header": { "title": "Q3 Metrics" },
|
|
192
|
+
"body": [
|
|
193
|
+
{ "primitive": "stat", "value": "42%", "label": "YoY growth", "trend": "+12% vs Q2" },
|
|
194
|
+
{ "primitive": "table", "headers": ["Quarter", "Revenue"], "rows": [["Q1", "10"], ["Q2", "14"]] },
|
|
195
|
+
{ "primitive": "chart", "chart_type": "bar", "categories": ["Q1", "Q2"],
|
|
196
|
+
"series": [{ "name": "Revenue", "values": [10, 14] }] }
|
|
197
|
+
]
|
|
198
|
+
}]
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 4. Process sequence
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"slides": [{
|
|
207
|
+
"header": { "title": "Our Process" },
|
|
208
|
+
"body": [{
|
|
209
|
+
"primitive": "sequence",
|
|
210
|
+
"orientation": "horizontal",
|
|
211
|
+
"steps": [
|
|
212
|
+
{ "label": "Discover", "description": "Understand the problem" },
|
|
213
|
+
{ "label": "Design", "description": "Sketch options" },
|
|
214
|
+
{ "label": "Ship", "description": "Release to users" }
|
|
215
|
+
]
|
|
216
|
+
}]
|
|
217
|
+
}]
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
See `examples/minimal.json` and `examples/full_catalog.json` for complete,
|
|
222
|
+
runnable specs (also used as test fixtures).
|
|
223
|
+
|
|
224
|
+
## Fonts and overflow validation
|
|
225
|
+
|
|
226
|
+
Overflow checking (`validate`'s layout errors, and the "shrink text on
|
|
227
|
+
overflow" behavior it protects against) reads real glyph advance widths via
|
|
228
|
+
`fonttools` — no rendering required. As of this release, no font is bundled
|
|
229
|
+
into the package yet (`src/compono/fonts/` is a placeholder); validation
|
|
230
|
+
falls back to a system font if one is found (e.g. `arial.ttf` on Windows),
|
|
231
|
+
and is skipped — not faked — with a warning if none is available. A bundled,
|
|
232
|
+
OFL-licensed safe-font list is planned before the first tagged release; this
|
|
233
|
+
section will list it once shipped.
|
|
234
|
+
|
|
235
|
+
## CLI
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
compono validate spec.json
|
|
239
|
+
compono render spec.json --template fractal -o deck.pptx
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Mirrors `validate`/`render_deck` exactly — useful for agent frameworks that
|
|
243
|
+
can only shell out rather than import Python.
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
246
|
+
|
|
247
|
+
See `CONTRIBUTING.md` for dev setup, branching, and code style. If you're
|
|
248
|
+
using Claude Code, `.claude/README.md` describes the build-workflow skill,
|
|
249
|
+
review subagent, and commit/format hooks set up for this repo.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "compono"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Agent-oriented, code-based PPTX generation library — Manim, but for PowerPoint."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Shaik-Hamzah123", email = "hamzah.shaik2003@gmail.com" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"fonttools>=4.65.0",
|
|
13
|
+
"pydantic>=2.13.5",
|
|
14
|
+
"python-pptx>=1.0.2",
|
|
15
|
+
"pyyaml>=6.0.3",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
compono = "compono.cli:main"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["uv_build>=0.11.17,<0.12.0"]
|
|
23
|
+
build-backend = "uv_build"
|
|
24
|
+
|
|
25
|
+
[dependency-groups]
|
|
26
|
+
dev = [
|
|
27
|
+
"mypy>=2.3.1",
|
|
28
|
+
"pytest>=9.1.1",
|
|
29
|
+
"ruff>=0.16.7",
|
|
30
|
+
"types-pyyaml>=6.0.12.20260906",
|
|
31
|
+
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""compono — agent-oriented, code-based PPTX generation library.
|
|
2
|
+
|
|
3
|
+
Public exports ONLY (COMPONO_PLAN.md section 10) — resolver/validator/template
|
|
4
|
+
loader stay internal, reached only through render_deck/validate.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from compono.render import (
|
|
8
|
+
DeckValidationError,
|
|
9
|
+
RenderReport,
|
|
10
|
+
ValidationReport,
|
|
11
|
+
render_deck,
|
|
12
|
+
validate,
|
|
13
|
+
)
|
|
14
|
+
from compono.schema import (
|
|
15
|
+
Chart,
|
|
16
|
+
Deck,
|
|
17
|
+
Grid,
|
|
18
|
+
Header,
|
|
19
|
+
Image,
|
|
20
|
+
Sequence,
|
|
21
|
+
Shape,
|
|
22
|
+
Slide,
|
|
23
|
+
Stat,
|
|
24
|
+
Table,
|
|
25
|
+
Text,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"Chart",
|
|
30
|
+
"Deck",
|
|
31
|
+
"DeckValidationError",
|
|
32
|
+
"Grid",
|
|
33
|
+
"Header",
|
|
34
|
+
"Image",
|
|
35
|
+
"RenderReport",
|
|
36
|
+
"Sequence",
|
|
37
|
+
"Shape",
|
|
38
|
+
"Slide",
|
|
39
|
+
"Stat",
|
|
40
|
+
"Table",
|
|
41
|
+
"Text",
|
|
42
|
+
"ValidationReport",
|
|
43
|
+
"render_deck",
|
|
44
|
+
"validate",
|
|
45
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Console-script entry point mirroring the render_deck/validate verbs.
|
|
2
|
+
|
|
3
|
+
compono validate spec.json
|
|
4
|
+
compono render spec.json --template fractal -o deck.pptx
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from compono.render import DeckValidationError, render_deck, validate
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_spec(path: str) -> dict:
|
|
18
|
+
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main(argv: list[str] | None = None) -> int:
|
|
22
|
+
parser = argparse.ArgumentParser(prog="compono")
|
|
23
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
24
|
+
|
|
25
|
+
validate_parser = subparsers.add_parser("validate", help="Validate a deck spec without rendering.")
|
|
26
|
+
validate_parser.add_argument("spec", help="Path to a JSON deck spec.")
|
|
27
|
+
|
|
28
|
+
render_parser = subparsers.add_parser("render", help="Render a deck spec to a .pptx file.")
|
|
29
|
+
render_parser.add_argument("spec", help="Path to a JSON deck spec.")
|
|
30
|
+
render_parser.add_argument("-o", "--output", default="deck.pptx", help="Output .pptx path.")
|
|
31
|
+
render_parser.add_argument(
|
|
32
|
+
"--template", default=None, help="Template name (reserved; the default template is used for now)."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
args = parser.parse_args(argv)
|
|
36
|
+
|
|
37
|
+
if args.command == "validate":
|
|
38
|
+
spec = _load_spec(args.spec)
|
|
39
|
+
validation_report = validate(spec)
|
|
40
|
+
print(
|
|
41
|
+
json.dumps(
|
|
42
|
+
{
|
|
43
|
+
"valid": validation_report.valid,
|
|
44
|
+
"errors": validation_report.errors,
|
|
45
|
+
"warnings": validation_report.warnings,
|
|
46
|
+
},
|
|
47
|
+
indent=2,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
return 0 if validation_report.valid else 1
|
|
51
|
+
|
|
52
|
+
if args.command == "render":
|
|
53
|
+
spec = _load_spec(args.spec)
|
|
54
|
+
try:
|
|
55
|
+
render_report = render_deck(spec, args.output)
|
|
56
|
+
except DeckValidationError as exc:
|
|
57
|
+
print(json.dumps({"valid": False, "errors": exc.errors}, indent=2), file=sys.stderr)
|
|
58
|
+
return 1
|
|
59
|
+
print(
|
|
60
|
+
json.dumps(
|
|
61
|
+
{"pptx_path": str(render_report.pptx_path), "warnings": render_report.warnings}, indent=2
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
return 0
|
|
65
|
+
|
|
66
|
+
return 1
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
raise SystemExit(main())
|
|
File without changes
|
|
File without changes
|