@deckspec/cli 0.1.4 → 0.1.5
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deckspec/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"src",
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"react": "^19.0.0",
|
|
19
19
|
"react-dom": "^19.0.0",
|
|
20
20
|
"zod": "^3.23.0",
|
|
21
|
-
"@deckspec/dsl": "0.1.
|
|
22
|
-
"@deckspec/schema": "0.1.
|
|
23
|
-
"@deckspec/renderer": "0.1.
|
|
21
|
+
"@deckspec/dsl": "0.1.5",
|
|
22
|
+
"@deckspec/schema": "0.1.5",
|
|
23
|
+
"@deckspec/renderer": "0.1.5"
|
|
24
24
|
},
|
|
25
25
|
"license": "Apache-2.0",
|
|
26
26
|
"repository": {
|
|
@@ -7,6 +7,12 @@ description: "Use this skill when the user asks to convert a DeckSpec deck to Po
|
|
|
7
7
|
|
|
8
8
|
Convert a DeckSpec deck.yaml to a PowerPoint (.pptx) file.
|
|
9
9
|
|
|
10
|
+
## How It Works
|
|
11
|
+
|
|
12
|
+
The conversion script (`deck-to-pptx.mjs`) reads `deck.yaml`, looks up each slide's `file:` pattern name in a `renderers` registry, and calls the matching renderer function to build the slide using pptxgenjs.
|
|
13
|
+
|
|
14
|
+
**Patterns without a registered renderer produce blank slides with a warning.** When you encounter this, you need to add a renderer function for that pattern.
|
|
15
|
+
|
|
10
16
|
## Prerequisites
|
|
11
17
|
|
|
12
18
|
- pptxgenjs installed: `npm install -D pptxgenjs`
|
|
@@ -21,16 +27,222 @@ node .claude/skills/deckspec-to-pptx/deck-to-pptx.mjs decks/<deck-name>/deck.yam
|
|
|
21
27
|
|
|
22
28
|
### 2. Check for warnings
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
```
|
|
31
|
+
⚠ No pptx renderer for pattern "my-pattern" — blank slide
|
|
32
|
+
```
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
If you see this, add a renderer for that pattern (see "Adding a Renderer" below).
|
|
27
35
|
|
|
28
36
|
### 3. Open and verify
|
|
29
37
|
|
|
30
|
-
Open the .pptx in PowerPoint or Google Slides to verify.
|
|
38
|
+
Open the .pptx in PowerPoint, Keynote, or Google Slides to verify.
|
|
39
|
+
|
|
40
|
+
## Adding a Renderer
|
|
41
|
+
|
|
42
|
+
When a pattern has no pptx renderer, you need to add one in `deck-to-pptx.mjs`.
|
|
43
|
+
|
|
44
|
+
### Step 1: Read the pattern's React source
|
|
45
|
+
|
|
46
|
+
Look at `themes/<theme>/patterns/<name>/index.tsx` to understand:
|
|
47
|
+
- The Zod schema (what `vars` are available)
|
|
48
|
+
- The layout structure (CSS classes, grid, cards, etc.)
|
|
49
|
+
- What content goes where
|
|
50
|
+
|
|
51
|
+
### Step 2: Write the renderer function
|
|
52
|
+
|
|
53
|
+
Add a function in the `// ─── Slide renderers ───` section of `deck-to-pptx.mjs`:
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
function renderMyPattern(slide, vars) {
|
|
57
|
+
const padX = px(64); // horizontal padding
|
|
58
|
+
const padY = px(48); // vertical padding
|
|
59
|
+
|
|
60
|
+
// Label (small uppercase text at top)
|
|
61
|
+
if (vars.label) {
|
|
62
|
+
slide.addText(vars.label.toUpperCase(), {
|
|
63
|
+
x: padX, y: padY, w: W - 2 * padX, h: 0.2,
|
|
64
|
+
fontSize: 9, fontFace: FONT_B, bold: true,
|
|
65
|
+
color: C.primary, charSpacing: 1.5, margin: 0,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Heading
|
|
70
|
+
slide.addText(vars.heading, {
|
|
71
|
+
x: padX, y: padY + 0.25, w: W - 2 * padX, h: 0.35,
|
|
72
|
+
fontSize: 18, fontFace: FONT_H, bold: true,
|
|
73
|
+
color: C.fg, margin: 0,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ... add more elements
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Step 3: Register the renderer
|
|
81
|
+
|
|
82
|
+
Add it to the `renderers` object at the bottom of the file:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
const renderers = {
|
|
86
|
+
// ... existing renderers
|
|
87
|
+
"my-pattern": renderMyPattern,
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Step 4: Re-run and verify
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
node .claude/skills/deckspec-to-pptx/deck-to-pptx.mjs decks/<deck>/deck.yaml -o output/<deck>.pptx
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## PptxGenJS API Reference
|
|
98
|
+
|
|
99
|
+
### Available Constants
|
|
100
|
+
|
|
101
|
+
| Constant | Description |
|
|
102
|
+
|----------|-------------|
|
|
103
|
+
| `C.primary` | Primary color (hex without #) |
|
|
104
|
+
| `C.fg` | Foreground/text color |
|
|
105
|
+
| `C.bg` | Background color |
|
|
106
|
+
| `C.muted` | Muted text color |
|
|
107
|
+
| `C.border` | Border color |
|
|
108
|
+
| `C.card` | Card background color |
|
|
109
|
+
| `FONT_H` | Heading font (Noto Sans JP) |
|
|
110
|
+
| `FONT_B` | Body font (Noto Sans JP) |
|
|
111
|
+
| `W` | Slide width in inches (10) |
|
|
112
|
+
| `H` | Slide height in inches (5.625) |
|
|
113
|
+
| `px(v)` | Convert CSS pixels to inches (1200px = 10in) |
|
|
114
|
+
|
|
115
|
+
### Core Methods
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
// Text
|
|
119
|
+
slide.addText("Hello", {
|
|
120
|
+
x: px(64), y: px(48), w: 5, h: 0.4,
|
|
121
|
+
fontSize: 18, fontFace: FONT_H, bold: true,
|
|
122
|
+
color: C.fg, align: "center", valign: "middle",
|
|
123
|
+
margin: 0,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Rich text (multiple styles in one box)
|
|
127
|
+
slide.addText([
|
|
128
|
+
{ text: "Bold part", options: { bold: true, fontSize: 14, color: C.fg } },
|
|
129
|
+
{ text: " normal part", options: { fontSize: 12, color: C.muted } },
|
|
130
|
+
], { x: 1, y: 1, w: 8, h: 0.5, margin: 0 });
|
|
131
|
+
|
|
132
|
+
// Shape
|
|
133
|
+
slide.addShape(pres.shapes.RECTANGLE, {
|
|
134
|
+
x: 1, y: 1, w: 3, h: 2,
|
|
135
|
+
fill: { color: C.card },
|
|
136
|
+
line: { color: C.border, width: 1 },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Line
|
|
140
|
+
slide.addShape(pres.shapes.LINE, {
|
|
141
|
+
x: 1, y: 1, w: 5, h: 0, // horizontal line (h=0)
|
|
142
|
+
line: { color: C.border, width: 0.5 },
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Circle/Oval
|
|
146
|
+
slide.addShape(pres.shapes.OVAL, {
|
|
147
|
+
x: 1, y: 1, w: 1, h: 1,
|
|
148
|
+
fill: { color: C.bg },
|
|
149
|
+
line: { color: C.fg, width: 1.5 },
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Table
|
|
153
|
+
slide.addTable(rows, {
|
|
154
|
+
x: px(64), y: 1, w: W - 2 * px(64),
|
|
155
|
+
colW: [1.5, 3, 3],
|
|
156
|
+
border: { type: "solid", pt: 0.5, color: C.border },
|
|
157
|
+
rowH: [0.4, 0.35, 0.35],
|
|
158
|
+
margin: [4, 8, 4, 8],
|
|
159
|
+
autoPage: false,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Image (base64)
|
|
163
|
+
slide.addImage({
|
|
164
|
+
data: "image/png;base64,...",
|
|
165
|
+
x: 1, y: 1, w: 4, h: 3,
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Important Rules
|
|
170
|
+
|
|
171
|
+
1. **Colors are 6-char hex WITHOUT `#`** — `"E5001F"` not `"#E5001F"`
|
|
172
|
+
2. **Use `charSpacing` not `letterSpacing`** — pptxgenjs uses its own property name
|
|
173
|
+
3. **Always set `margin: 0`** on text boxes for precise alignment
|
|
174
|
+
4. **Never reuse option objects** between `addText`/`addShape` calls — pptxgenjs mutates them
|
|
175
|
+
5. **`breakLine: true`** in rich text options to force line break after that segment
|
|
176
|
+
6. **`strike: true`** for strikethrough text (useful for "before" prices)
|
|
177
|
+
7. **`highlight: "FF0000"`** to add text highlight/background color on rich text segments
|
|
178
|
+
|
|
179
|
+
### Common Layout Patterns
|
|
180
|
+
|
|
181
|
+
**Label + Heading + Content** (most common):
|
|
182
|
+
```javascript
|
|
183
|
+
// Top: small label
|
|
184
|
+
// Below: large heading
|
|
185
|
+
// Rest: content area
|
|
186
|
+
const padX = px(64);
|
|
187
|
+
const padY = px(48);
|
|
188
|
+
const contentTop = padY + 0.85; // below heading
|
|
189
|
+
const contentBot = H - padY;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Card Grid**:
|
|
193
|
+
```javascript
|
|
194
|
+
const items = vars.items;
|
|
195
|
+
const gap = px(1);
|
|
196
|
+
const cardW = (W - 2 * padX - gap * (items.length - 1)) / items.length;
|
|
197
|
+
|
|
198
|
+
items.forEach((item, i) => {
|
|
199
|
+
const cx = padX + i * (cardW + gap);
|
|
200
|
+
slide.addShape(pres.shapes.RECTANGLE, {
|
|
201
|
+
x: cx, y: cardTop, w: cardW, h: cardH,
|
|
202
|
+
fill: { color: C.card },
|
|
203
|
+
});
|
|
204
|
+
// Add text inside card...
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Two-Column Layout**:
|
|
209
|
+
```javascript
|
|
210
|
+
const leftW = (W - 2 * padX) * 0.5;
|
|
211
|
+
const rightX = padX + leftW + px(48);
|
|
212
|
+
const rightW = W - rightX - padX;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Helper Functions
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
// resolveImage(filename) — converts local file to base64 data URI
|
|
219
|
+
const imgData = resolveImage(vars.image);
|
|
220
|
+
if (imgData) {
|
|
221
|
+
slide.addImage({ data: imgData, x: 1, y: 1, w: 4, h: 3 });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// getImageSize(filename) — returns { w, h } in pixels (macOS sips)
|
|
225
|
+
const size = getImageSize(vars.image);
|
|
226
|
+
|
|
227
|
+
// containImage(imgW, imgH, boxW, boxH) — fit preserving aspect ratio
|
|
228
|
+
const fit = containImage(size.w, size.h, boxW, boxH);
|
|
229
|
+
slide.addImage({
|
|
230
|
+
data: imgData,
|
|
231
|
+
x: boxX + fit.offX, y: boxY + fit.offY,
|
|
232
|
+
w: fit.w, h: fit.h,
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Registered Renderers
|
|
237
|
+
|
|
238
|
+
The following patterns have pptx renderers pre-built:
|
|
31
239
|
|
|
32
|
-
|
|
240
|
+
| Pattern | Layout |
|
|
241
|
+
|---------|--------|
|
|
242
|
+
| `price-before-after` | Before→After price comparison cards |
|
|
243
|
+
| `comparison-table` | Multi-column table with icon indicators |
|
|
244
|
+
| `image-full` | Header + full-width image |
|
|
245
|
+
| `conclusion-summary` | Logo + heading + summary points |
|
|
246
|
+
| `closing-triad` | Two-column: message + triangle diagram |
|
|
33
247
|
|
|
34
|
-
|
|
35
|
-
- Local images are embedded as base64
|
|
36
|
-
- Not all patterns have pptx renderers yet — unregistered patterns produce blank slides
|
|
248
|
+
All other patterns will produce blank slides until you add a renderer.
|