@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.4",
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.4",
22
- "@deckspec/schema": "0.1.4",
23
- "@deckspec/renderer": "0.1.4"
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
- The script warns for unregistered patterns: `⚠ No pptx renderer for pattern "<name>" — blank slide`
30
+ ```
31
+ ⚠ No pptx renderer for pattern "my-pattern" — blank slide
32
+ ```
25
33
 
26
- To fix, add a renderer function in `.claude/skills/deckspec-to-pptx/deck-to-pptx.mjs`.
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
- ## Notes
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
- - Colors and fonts are loaded from `themes/<theme>/tokens.json`
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.