@stonedeck/core 0.7.0

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.
Files changed (110) hide show
  1. package/dist/emitter.d.ts +6 -0
  2. package/dist/emitter.d.ts.map +1 -0
  3. package/dist/emitter.js +107 -0
  4. package/dist/emitter.js.map +1 -0
  5. package/dist/emitter.test.d.ts +2 -0
  6. package/dist/emitter.test.d.ts.map +1 -0
  7. package/dist/emitter.test.js +70 -0
  8. package/dist/emitter.test.js.map +1 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +8 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/interface/export-plugin.d.ts +5 -0
  14. package/dist/interface/export-plugin.d.ts.map +1 -0
  15. package/dist/interface/export-plugin.js +2 -0
  16. package/dist/interface/export-plugin.js.map +1 -0
  17. package/dist/layouts/layout-generator.d.ts +14 -0
  18. package/dist/layouts/layout-generator.d.ts.map +1 -0
  19. package/dist/layouts/layout-generator.js +110 -0
  20. package/dist/layouts/layout-generator.js.map +1 -0
  21. package/dist/layouts/registry.json +290 -0
  22. package/dist/layouts/validator.d.ts +7 -0
  23. package/dist/layouts/validator.d.ts.map +1 -0
  24. package/dist/layouts/validator.js +27 -0
  25. package/dist/layouts/validator.js.map +1 -0
  26. package/dist/layouts/validator.test.d.ts +2 -0
  27. package/dist/layouts/validator.test.d.ts.map +1 -0
  28. package/dist/layouts/validator.test.js +19 -0
  29. package/dist/layouts/validator.test.js.map +1 -0
  30. package/dist/models/ir.d.ts +91 -0
  31. package/dist/models/ir.d.ts.map +1 -0
  32. package/dist/models/ir.js +2 -0
  33. package/dist/models/ir.js.map +1 -0
  34. package/dist/parser/image-mapper.d.ts +8 -0
  35. package/dist/parser/image-mapper.d.ts.map +1 -0
  36. package/dist/parser/image-mapper.js +15 -0
  37. package/dist/parser/image-mapper.js.map +1 -0
  38. package/dist/parser/list-mapper.d.ts +3 -0
  39. package/dist/parser/list-mapper.d.ts.map +1 -0
  40. package/dist/parser/list-mapper.js +24 -0
  41. package/dist/parser/list-mapper.js.map +1 -0
  42. package/dist/parser/list-mapper.test.d.ts +2 -0
  43. package/dist/parser/list-mapper.test.d.ts.map +1 -0
  44. package/dist/parser/list-mapper.test.js +26 -0
  45. package/dist/parser/list-mapper.test.js.map +1 -0
  46. package/dist/parser/manifesto.d.ts +9 -0
  47. package/dist/parser/manifesto.d.ts.map +1 -0
  48. package/dist/parser/manifesto.js +40 -0
  49. package/dist/parser/manifesto.js.map +1 -0
  50. package/dist/parser/manifesto.test.d.ts +2 -0
  51. package/dist/parser/manifesto.test.d.ts.map +1 -0
  52. package/dist/parser/manifesto.test.js +32 -0
  53. package/dist/parser/manifesto.test.js.map +1 -0
  54. package/dist/parser/table-mapper.d.ts +7 -0
  55. package/dist/parser/table-mapper.d.ts.map +1 -0
  56. package/dist/parser/table-mapper.js +33 -0
  57. package/dist/parser/table-mapper.js.map +1 -0
  58. package/dist/parser/tokenizer.d.ts +4 -0
  59. package/dist/parser/tokenizer.d.ts.map +1 -0
  60. package/dist/parser/tokenizer.js +103 -0
  61. package/dist/parser/tokenizer.js.map +1 -0
  62. package/dist/parser/tokenizer.test.d.ts +2 -0
  63. package/dist/parser/tokenizer.test.d.ts.map +1 -0
  64. package/dist/parser/tokenizer.test.js +31 -0
  65. package/dist/parser/tokenizer.test.js.map +1 -0
  66. package/dist/processor.d.ts +3 -0
  67. package/dist/processor.d.ts.map +1 -0
  68. package/dist/processor.js +5 -0
  69. package/dist/processor.js.map +1 -0
  70. package/dist/resolver/metrics.d.ts +27 -0
  71. package/dist/resolver/metrics.d.ts.map +1 -0
  72. package/dist/resolver/metrics.js +43 -0
  73. package/dist/resolver/metrics.js.map +1 -0
  74. package/dist/resolver/metrics.test.d.ts +2 -0
  75. package/dist/resolver/metrics.test.d.ts.map +1 -0
  76. package/dist/resolver/metrics.test.js +23 -0
  77. package/dist/resolver/metrics.test.js.map +1 -0
  78. package/dist/resolver/theme-loader.d.ts +17 -0
  79. package/dist/resolver/theme-loader.d.ts.map +1 -0
  80. package/dist/resolver/theme-loader.js +110 -0
  81. package/dist/resolver/theme-loader.js.map +1 -0
  82. package/dist/resolver/theme-loader.test.d.ts +2 -0
  83. package/dist/resolver/theme-loader.test.d.ts.map +1 -0
  84. package/dist/resolver/theme-loader.test.js +60 -0
  85. package/dist/resolver/theme-loader.test.js.map +1 -0
  86. package/package.json +19 -0
  87. package/src/emitter.ts +118 -0
  88. package/src/index.ts +7 -0
  89. package/src/interface/export-plugin.ts +5 -0
  90. package/src/layouts/layout-generator.ts +116 -0
  91. package/src/layouts/registry.json +290 -0
  92. package/src/layouts/validator.ts +31 -0
  93. package/src/models/ir.ts +98 -0
  94. package/src/parser/image-mapper.ts +14 -0
  95. package/src/parser/list-mapper.ts +28 -0
  96. package/src/parser/manifesto.ts +49 -0
  97. package/src/parser/table-mapper.ts +38 -0
  98. package/src/parser/tokenizer.ts +126 -0
  99. package/src/processor.ts +6 -0
  100. package/src/resolver/metrics.ts +64 -0
  101. package/src/resolver/theme-loader.ts +122 -0
  102. package/src/themes/academico.yaml +25 -0
  103. package/src/themes/corporativo.yaml +25 -0
  104. package/src/themes/dark_mode.yaml +26 -0
  105. package/src/themes/minimalista.yaml +25 -0
  106. package/src/themes/moderno.yaml +26 -0
  107. package/test/cycle2_markdown.test.ts +36 -0
  108. package/test/cycle2_slides.test.ts +52 -0
  109. package/tsconfig.json +17 -0
  110. package/tsconfig.tsbuildinfo +1 -0
package/src/emitter.ts ADDED
@@ -0,0 +1,118 @@
1
+ import { parseManifesto } from './parser/manifesto.js';
2
+ import { parseSlides } from './parser/tokenizer.js';
3
+ import { ThemeLoader } from './resolver/theme-loader.js';
4
+ import { validateLayout, getLayout } from './layouts/validator.js';
5
+ import { StoneDeckIR, Slide, SlotContent, ListItem, TableCell } from './models/ir.js';
6
+ import { metricsCalculator } from './resolver/metrics.js';
7
+ import { mapMarkdownLists } from './parser/list-mapper.js';
8
+ import { mapMarkdownTable } from './parser/table-mapper.js';
9
+ import { mapMarkdownImage } from './parser/image-mapper.js';
10
+ import * as path from 'path';
11
+
12
+ /**
13
+ * Emitter orchestrates the parsing and theme resolution to produce the final IR.
14
+ */
15
+ export function emitIR(content: string, filePath: string, themeOverride?: string): StoneDeckIR {
16
+ const basePath = path.dirname(filePath);
17
+ const globalWarnings: string[] = [];
18
+
19
+ // 1. Parse Manifesto
20
+ const { manifesto, remainingContent } = parseManifesto(content);
21
+
22
+ // 2. Parse Slides
23
+ const slides = parseSlides(remainingContent);
24
+
25
+ // 4. Resolve Temas e Validar Layouts
26
+ const themeToLoad = themeOverride || manifesto.theme;
27
+ const theme = ThemeLoader.load(themeToLoad, basePath);
28
+
29
+ const resolvedSlides = slides.map((slide, slideIdx) => {
30
+ // Validate Layout
31
+ const validation = validateLayout(slide.layout_id, slide.slots.length);
32
+ const warnings: string[] = [];
33
+ if (!validation.valid) {
34
+ warnings.push(validation.error!);
35
+ }
36
+
37
+ const layout = getLayout(slide.layout_id, slide.slots.length);
38
+ // Merge Theme Defaults
39
+ const defaults = theme.defaults || {};
40
+ const mergedStyle = { ...defaults, ...slide.style };
41
+
42
+ // Resolve Style with Theme
43
+ const resolvedStyle = ThemeLoader.resolveStyle(mergedStyle, theme);
44
+
45
+ // Process Slots (List, Table, Image detection)
46
+ const processedSlots: SlotContent[] = slide.slots.map(slot => {
47
+ if (slot.type === 'markdown') {
48
+ // Try Table
49
+ const tableRows = mapMarkdownTable(slot.raw);
50
+ if (tableRows) return { type: 'table', rows: tableRows };
51
+
52
+ // Try Image
53
+ const img = mapMarkdownImage(slot.raw);
54
+ if (img) return { type: 'image', src: img.src, ...(img.alt ? { alt: img.alt } : {}) } as SlotContent;
55
+
56
+ // Try List - but only convert if ALL content is list items
57
+ const listItems = mapMarkdownLists(slot.raw);
58
+ if (listItems.length > 0) {
59
+ // Count non-empty, non-list lines
60
+ const lines = slot.raw.split(/\r?\n/).filter(l => l.trim().length > 0);
61
+ const hasOnlyListItems = lines.every(line => {
62
+ // Check if line matches list pattern
63
+ return /^\s*([-*]|\d+\.)\s+/.test(line);
64
+ });
65
+
66
+ if (hasOnlyListItems) {
67
+ return { type: 'list', items: listItems };
68
+ }
69
+ }
70
+ }
71
+ return slot;
72
+ });
73
+
74
+ // Calculate Metrics and Overflow
75
+ const heights: number[] = [];
76
+ if (layout) {
77
+ processedSlots.forEach((slot, slotIdx) => {
78
+ const slotDef = layout.slots[slotIdx];
79
+ if (slotDef) {
80
+ let contentToMeasure = '';
81
+ if (slot.type === 'list') {
82
+ contentToMeasure = slot.items.map((i: ListItem) => i.text).join('\n');
83
+ } else if (slot.type === 'markdown') {
84
+ contentToMeasure = slot.raw;
85
+ } else if (slot.type === 'table') {
86
+ contentToMeasure = slot.rows.map((r: TableCell[]) => r.map((c: TableCell) => c.text).join(' ')).join('\n');
87
+ } else if (slot.type === 'image') {
88
+ contentToMeasure = slot.alt || slot.src;
89
+ }
90
+
91
+ const estimatedHeight = metricsCalculator.estimateMarkdownHeight(contentToMeasure, resolvedStyle, slotDef.width);
92
+ heights.push(estimatedHeight);
93
+
94
+ if (estimatedHeight > slotDef.height) {
95
+ warnings.push(`Overflow Warning: Content in slot "${slotDef.id}" (slide ${slideIdx + 1}) exceeds defined height (${estimatedHeight.toFixed(1)}pt > ${slotDef.height}pt)`);
96
+ }
97
+ }
98
+ });
99
+ }
100
+
101
+ return {
102
+ ...slide,
103
+ style: resolvedStyle,
104
+ slots: processedSlots,
105
+ warnings: warnings.length > 0 ? warnings : undefined,
106
+ metrics: {
107
+ heights
108
+ }
109
+ };
110
+ });
111
+
112
+ return {
113
+ manifesto,
114
+ slides: resolvedSlides,
115
+ basePath,
116
+ warnings: globalWarnings.length > 0 ? globalWarnings : undefined
117
+ };
118
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './models/ir.js';
2
+ export * from './emitter.js';
3
+ export * from './parser/tokenizer.js';
4
+ export * from './parser/manifesto.js';
5
+ export * from './layouts/validator.js';
6
+ export * from './interface/export-plugin.js';
7
+ export * from './processor.js'; // Will create this next
@@ -0,0 +1,5 @@
1
+ import { StoneDeckIR } from '../models/ir.js';
2
+
3
+ export interface ExportPlugin {
4
+ generate(ir: StoneDeckIR, outputPath: string, options?: any): Promise<void>;
5
+ }
@@ -0,0 +1,116 @@
1
+
2
+ import { Layout, LayoutSlot } from '../models/ir.js';
3
+
4
+ export class LayoutGenerator {
5
+ private static CANVAS_WIDTH = 720;
6
+ private static CANVAS_HEIGHT = 405;
7
+ private static SAFETY_MARGIN = 30;
8
+
9
+ /**
10
+ * Generates a dynamic layout based on valid base types.
11
+ */
12
+ static generate(layoutId: string, slotCount: number): Layout | null {
13
+ if (layoutId === 'content') {
14
+ return this.generateContentGrid(slotCount);
15
+ }
16
+ if (layoutId === 'title-and-content') {
17
+ return this.generateTitleAndContent(slotCount);
18
+ }
19
+ return null;
20
+ }
21
+
22
+ private static generateContentGrid(count: number): Layout {
23
+ // Full canvas usage
24
+ const bounds = {
25
+ x: this.SAFETY_MARGIN,
26
+ y: this.SAFETY_MARGIN,
27
+ w: this.CANVAS_WIDTH - (this.SAFETY_MARGIN * 2),
28
+ h: this.CANVAS_HEIGHT - (this.SAFETY_MARGIN * 2)
29
+ };
30
+
31
+ return {
32
+ id: 'content',
33
+ slots: this.calculateGridSlots(count, bounds)
34
+ };
35
+ }
36
+
37
+ private static generateTitleAndContent(count: number): Layout {
38
+ // Slot 0 is always Title (Top)
39
+ // Slots 1..N are Body Content (Bottom)
40
+
41
+ const titleHeight = 120;
42
+ const spacing = 20;
43
+
44
+ const titleSlot: LayoutSlot = {
45
+ id: 'title',
46
+ x: this.SAFETY_MARGIN,
47
+ y: 30,
48
+ width: this.CANVAS_WIDTH - (this.SAFETY_MARGIN * 2),
49
+ height: titleHeight
50
+ };
51
+
52
+ if (count <= 1) {
53
+ return {
54
+ id: 'title-and-content',
55
+ slots: [titleSlot]
56
+ };
57
+ }
58
+
59
+ const bodyBounds = {
60
+ x: this.SAFETY_MARGIN,
61
+ y: 30 + titleHeight + spacing,
62
+ w: this.CANVAS_WIDTH - (this.SAFETY_MARGIN * 2),
63
+ h: this.CANVAS_HEIGHT - (30 + titleHeight + spacing) - 30
64
+ };
65
+
66
+ const bodySlots = this.calculateGridSlots(count - 1, bodyBounds, 1); // offset index by 1
67
+
68
+ return {
69
+ id: 'title-and-content',
70
+ slots: [titleSlot, ...bodySlots]
71
+ };
72
+ }
73
+
74
+ private static calculateGridSlots(count: number, bounds: { x: number, y: number, w: number, h: number }, indexOffset: number = 0): LayoutSlot[] {
75
+ const slots: LayoutSlot[] = [];
76
+ if (count === 0) return slots;
77
+
78
+ // Simple Grid Logic
79
+ // 1: 1x1
80
+ // 2: 2 cols
81
+ // 3: 3 cols
82
+ // 4: 2x2
83
+ // 5: 3x2 (last row 2 center? No, simple grid for now)
84
+ // 6: 3x2
85
+ // 7-8: 4x2
86
+ // 9: 3x3
87
+
88
+ let cols = 1;
89
+ let rows = 1;
90
+
91
+ if (count === 2) { cols = 2; }
92
+ else if (count === 3) { cols = 3; }
93
+ else if (count === 4) { cols = 2; rows = 2; }
94
+ else if (count === 5 || count === 6) { cols = 3; rows = 2; }
95
+ else if (count > 6) { cols = Math.ceil(Math.sqrt(count)); rows = Math.ceil(count / cols); }
96
+
97
+ const cellW = (bounds.w - (cols - 1) * 20) / cols;
98
+ const cellH = (bounds.h - (rows - 1) * 20) / rows;
99
+ const gap = 20;
100
+
101
+ for (let i = 0; i < count; i++) {
102
+ const r = Math.floor(i / cols);
103
+ const c = i % cols;
104
+
105
+ slots.push({
106
+ id: `slot_${i + indexOffset}`,
107
+ x: bounds.x + c * (cellW + gap),
108
+ y: bounds.y + r * (cellH + gap),
109
+ width: cellW,
110
+ height: cellH
111
+ });
112
+ }
113
+
114
+ return slots;
115
+ }
116
+ }
@@ -0,0 +1,290 @@
1
+ {
2
+ "layouts": [
3
+ {
4
+ "id": "section-header",
5
+ "slots": [
6
+ {
7
+ "id": "title",
8
+ "x": 50,
9
+ "y": 180,
10
+ "width": 620,
11
+ "height": 60
12
+ }
13
+ ]
14
+ },
15
+ {
16
+ "id": "title",
17
+ "slots": [
18
+ {
19
+ "id": "title",
20
+ "x": 50,
21
+ "y": 120,
22
+ "width": 620,
23
+ "height": 100
24
+ },
25
+ {
26
+ "id": "subtitle",
27
+ "x": 50,
28
+ "y": 230,
29
+ "width": 620,
30
+ "height": 80
31
+ }
32
+ ]
33
+ },
34
+ {
35
+ "id": "title-and-content",
36
+ "slots": [
37
+ {
38
+ "id": "title",
39
+ "x": 50,
40
+ "y": 30,
41
+ "width": 620,
42
+ "height": 60
43
+ },
44
+ {
45
+ "id": "body",
46
+ "x": 50,
47
+ "y": 100,
48
+ "width": 620,
49
+ "height": 280
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "id": "title-and-content",
55
+ "slots": [
56
+ {
57
+ "id": "title",
58
+ "x": 50,
59
+ "y": 30,
60
+ "width": 620,
61
+ "height": 60
62
+ },
63
+ {
64
+ "id": "col1",
65
+ "x": 50,
66
+ "y": 100,
67
+ "width": 300,
68
+ "height": 280
69
+ },
70
+ {
71
+ "id": "col2",
72
+ "x": 370,
73
+ "y": 100,
74
+ "width": 300,
75
+ "height": 280
76
+ }
77
+ ]
78
+ },
79
+ {
80
+ "id": "title-and-content",
81
+ "slots": [
82
+ {
83
+ "id": "title",
84
+ "x": 50,
85
+ "y": 30,
86
+ "width": 620,
87
+ "height": 60
88
+ },
89
+ {
90
+ "id": "col1",
91
+ "x": 50,
92
+ "y": 100,
93
+ "width": 190,
94
+ "height": 280
95
+ },
96
+ {
97
+ "id": "col2",
98
+ "x": 260,
99
+ "y": 100,
100
+ "width": 190,
101
+ "height": 280
102
+ },
103
+ {
104
+ "id": "col3",
105
+ "x": 470,
106
+ "y": 100,
107
+ "width": 190,
108
+ "height": 280
109
+ }
110
+ ]
111
+ },
112
+ {
113
+ "id": "title-and-content",
114
+ "slots": [
115
+ {
116
+ "id": "title",
117
+ "x": 50,
118
+ "y": 30,
119
+ "width": 620,
120
+ "height": 60
121
+ },
122
+ {
123
+ "id": "card1",
124
+ "x": 50,
125
+ "y": 100,
126
+ "width": 300,
127
+ "height": 135
128
+ },
129
+ {
130
+ "id": "card2",
131
+ "x": 370,
132
+ "y": 100,
133
+ "width": 300,
134
+ "height": 135
135
+ },
136
+ {
137
+ "id": "card3",
138
+ "x": 50,
139
+ "y": 245,
140
+ "width": 300,
141
+ "height": 135
142
+ },
143
+ {
144
+ "id": "card4",
145
+ "x": 370,
146
+ "y": 245,
147
+ "width": 300,
148
+ "height": 135
149
+ }
150
+ ]
151
+ },
152
+ {
153
+ "id": "content",
154
+ "slots": [
155
+ {
156
+ "id": "body",
157
+ "x": 50,
158
+ "y": 50,
159
+ "width": 620,
160
+ "height": 300
161
+ }
162
+ ]
163
+ },
164
+ {
165
+ "id": "content",
166
+ "slots": [
167
+ {
168
+ "id": "col1",
169
+ "x": 50,
170
+ "y": 50,
171
+ "width": 300,
172
+ "height": 300
173
+ },
174
+ {
175
+ "id": "col2",
176
+ "x": 370,
177
+ "y": 50,
178
+ "width": 300,
179
+ "height": 300
180
+ }
181
+ ]
182
+ },
183
+ {
184
+ "id": "content",
185
+ "slots": [
186
+ {
187
+ "id": "col1",
188
+ "x": 50,
189
+ "y": 50,
190
+ "width": 190,
191
+ "height": 300
192
+ },
193
+ {
194
+ "id": "col2",
195
+ "x": 260,
196
+ "y": 50,
197
+ "width": 190,
198
+ "height": 300
199
+ },
200
+ {
201
+ "id": "col3",
202
+ "x": 470,
203
+ "y": 50,
204
+ "width": 190,
205
+ "height": 300
206
+ }
207
+ ]
208
+ },
209
+ {
210
+ "id": "content",
211
+ "slots": [
212
+ {
213
+ "id": "card1",
214
+ "x": 50,
215
+ "y": 50,
216
+ "width": 300,
217
+ "height": 140
218
+ },
219
+ {
220
+ "id": "card2",
221
+ "x": 370,
222
+ "y": 50,
223
+ "width": 300,
224
+ "height": 140
225
+ },
226
+ {
227
+ "id": "card3",
228
+ "x": 50,
229
+ "y": 210,
230
+ "width": 300,
231
+ "height": 140
232
+ },
233
+ {
234
+ "id": "card4",
235
+ "x": 370,
236
+ "y": 210,
237
+ "width": 300,
238
+ "height": 140
239
+ }
240
+ ]
241
+ },
242
+ {
243
+ "id": "content",
244
+ "slots": [
245
+ {
246
+ "id": "card1",
247
+ "x": 50,
248
+ "y": 50,
249
+ "width": 190,
250
+ "height": 140
251
+ },
252
+ {
253
+ "id": "card2",
254
+ "x": 260,
255
+ "y": 50,
256
+ "width": 190,
257
+ "height": 140
258
+ },
259
+ {
260
+ "id": "card3",
261
+ "x": 470,
262
+ "y": 50,
263
+ "width": 190,
264
+ "height": 140
265
+ },
266
+ {
267
+ "id": "card4",
268
+ "x": 50,
269
+ "y": 210,
270
+ "width": 190,
271
+ "height": 140
272
+ },
273
+ {
274
+ "id": "card5",
275
+ "x": 260,
276
+ "y": 210,
277
+ "width": 190,
278
+ "height": 140
279
+ },
280
+ {
281
+ "id": "card6",
282
+ "x": 470,
283
+ "y": 210,
284
+ "width": 190,
285
+ "height": 140
286
+ }
287
+ ]
288
+ }
289
+ ]
290
+ }
@@ -0,0 +1,31 @@
1
+ import { Layout } from '../models/ir.js';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { LayoutGenerator } from './layout-generator.js';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const registryPath = path.join(__dirname, 'registry.json');
11
+ const registry = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));
12
+
13
+ export function validateLayout(id: string, slotCount: number): { valid: boolean; error?: string } {
14
+ const layout = getLayout(id, slotCount);
15
+ if (!layout) {
16
+ return { valid: false, error: `Layout '${id}' not found for ${slotCount} slots.` };
17
+ }
18
+ return { valid: true };
19
+ }
20
+
21
+ export function getLayout(id: string, slotCount: number): Layout | undefined {
22
+ // 1. Try Registry First (Static definitions override dynamic)
23
+ const layout = registry.layouts.find((l: any) => l.id === id && l.slots.length === slotCount);
24
+ if (layout) return layout;
25
+
26
+ // 2. Try Dynamic Generator
27
+ const dynamic = LayoutGenerator.generate(id, slotCount);
28
+ if (dynamic) return dynamic;
29
+
30
+ return undefined;
31
+ }
@@ -0,0 +1,98 @@
1
+ export interface StoneDeckManifesto {
2
+ StoneDeck: boolean;
3
+ title: string;
4
+ subtitle?: string;
5
+ theme: string;
6
+ author?: string;
7
+ }
8
+
9
+ export interface ListItem {
10
+ text: string;
11
+ level: number;
12
+ bullet_type: 'dot' | 'square' | 'arrow' | 'dash' | 'number' | string;
13
+ }
14
+
15
+ export interface TableCell {
16
+ text: string;
17
+ isHeader: boolean;
18
+ align?: 'left' | 'center' | 'right';
19
+ }
20
+
21
+ export interface SlideStyle {
22
+ background?: {
23
+ type: 'color' | 'image' | 'gradient';
24
+ value?: string;
25
+ src?: string;
26
+ opacity?: number;
27
+ fit?: 'cover' | 'contain';
28
+ colors?: string[];
29
+ direction?: string;
30
+ };
31
+ content_align?: {
32
+ horizontal?: 'left' | 'center' | 'right';
33
+ vertical?: 'top' | 'middle' | 'bottom';
34
+ };
35
+ card?: {
36
+ background?: string;
37
+ radius?: string;
38
+ border?: string;
39
+ shadow?: boolean;
40
+ content_align?: {
41
+ horizontal?: 'left' | 'center' | 'right';
42
+ vertical?: 'top' | 'middle' | 'bottom';
43
+ };
44
+ };
45
+ list?: {
46
+ bullet_type?: string;
47
+ bullet_color?: string;
48
+ spacing?: string;
49
+ indent?: string;
50
+ };
51
+ [key: string]: any;
52
+ }
53
+
54
+ export type SlotContent = {
55
+ type: 'markdown';
56
+ raw: string;
57
+ } | {
58
+ type: 'list';
59
+ items: ListItem[];
60
+ } | {
61
+ type: 'table';
62
+ rows: TableCell[][];
63
+ } | {
64
+ type: 'image';
65
+ src: string;
66
+ alt?: string;
67
+ };
68
+
69
+ export interface Slide {
70
+ layout_id: string;
71
+ title?: string; // Optional slide title
72
+ style: SlideStyle;
73
+ slots: SlotContent[]; // Content of each slot (Structured)
74
+ warnings?: string[] | undefined;
75
+ metrics?: {
76
+ heights: number[]; // Height of each slot in pt
77
+ } | undefined;
78
+ }
79
+
80
+ export interface StoneDeckIR {
81
+ manifesto: StoneDeckManifesto;
82
+ slides: Slide[];
83
+ basePath: string; // Base directory for resource resolution (images, themes)
84
+ warnings?: string[] | undefined;
85
+ }
86
+
87
+ export interface LayoutSlot {
88
+ id: string;
89
+ x: number;
90
+ y: number;
91
+ width: number;
92
+ height: number;
93
+ }
94
+
95
+ export interface Layout {
96
+ id: string;
97
+ slots: LayoutSlot[];
98
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Detects a Markdown image in a string.
3
+ */
4
+ export function mapMarkdownImage(markdown: string): { src: string; alt?: string } | null {
5
+ // Regex for ![alt](src)
6
+ const match = markdown.trim().match(/^!\[(.*?)\]\((.*?)\)$/);
7
+ if (match && match[1] !== undefined && match[2] !== undefined) {
8
+ return {
9
+ alt: match[1],
10
+ src: match[2]
11
+ };
12
+ }
13
+ return null;
14
+ }