@lemonppt/renderer 1.0.7 → 1.0.8

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 (84) hide show
  1. package/dist/browser-export-driver.d.ts +28 -0
  2. package/dist/browser-export-driver.d.ts.map +1 -0
  3. package/dist/browser-export-driver.js +115 -0
  4. package/dist/browser-export-driver.js.map +1 -0
  5. package/dist/browser-export.d.ts +66 -0
  6. package/dist/browser-export.d.ts.map +1 -0
  7. package/dist/browser-export.js +176 -0
  8. package/dist/browser-export.js.map +1 -0
  9. package/dist/client/browser-export.js +1 -0
  10. package/dist/client/client-render.js +21 -21
  11. package/dist/client/theme-echarts.js +27 -27
  12. package/dist/export-pdf.d.ts.map +1 -1
  13. package/dist/export-pdf.js +6 -3
  14. package/dist/export-pdf.js.map +1 -1
  15. package/dist/export-pptx-screenshot.d.ts +36 -0
  16. package/dist/export-pptx-screenshot.d.ts.map +1 -0
  17. package/dist/export-pptx-screenshot.js +74 -0
  18. package/dist/export-pptx-screenshot.js.map +1 -0
  19. package/dist/index.d.ts +3 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/normalize-goal.d.ts +6 -0
  24. package/dist/normalize-goal.d.ts.map +1 -0
  25. package/dist/normalize-goal.js +12 -0
  26. package/dist/normalize-goal.js.map +1 -0
  27. package/dist/preview-server.d.ts +9 -0
  28. package/dist/preview-server.d.ts.map +1 -0
  29. package/dist/preview-server.js +81 -0
  30. package/dist/preview-server.js.map +1 -0
  31. package/dist/render-editor-data.js +2 -2
  32. package/dist/render-editor-data.js.map +1 -1
  33. package/dist/render.d.ts.map +1 -1
  34. package/dist/render.js +14 -4
  35. package/dist/render.js.map +1 -1
  36. package/package.json +8 -5
  37. package/dist/export-pptx.d.ts +0 -1167
  38. package/dist/export-pptx.d.ts.map +0 -1
  39. package/dist/export-pptx.js +0 -3007
  40. package/dist/export-pptx.js.map +0 -1
  41. package/dist/theme01-pptx.d.ts +0 -12
  42. package/dist/theme01-pptx.d.ts.map +0 -1
  43. package/dist/theme01-pptx.js +0 -2957
  44. package/dist/theme01-pptx.js.map +0 -1
  45. package/dist/theme02-pptx.d.ts +0 -5
  46. package/dist/theme02-pptx.d.ts.map +0 -1
  47. package/dist/theme02-pptx.js +0 -1063
  48. package/dist/theme02-pptx.js.map +0 -1
  49. package/dist/theme03-pptx.d.ts +0 -5
  50. package/dist/theme03-pptx.d.ts.map +0 -1
  51. package/dist/theme03-pptx.js +0 -3279
  52. package/dist/theme03-pptx.js.map +0 -1
  53. package/dist/theme04-pptx.d.ts +0 -6
  54. package/dist/theme04-pptx.d.ts.map +0 -1
  55. package/dist/theme04-pptx.js +0 -5496
  56. package/dist/theme04-pptx.js.map +0 -1
  57. package/dist/theme05-pptx.d.ts +0 -5
  58. package/dist/theme05-pptx.d.ts.map +0 -1
  59. package/dist/theme05-pptx.js +0 -3152
  60. package/dist/theme05-pptx.js.map +0 -1
  61. package/dist/theme06-pptx.d.ts +0 -124
  62. package/dist/theme06-pptx.d.ts.map +0 -1
  63. package/dist/theme06-pptx.js +0 -8154
  64. package/dist/theme06-pptx.js.map +0 -1
  65. package/dist/theme07-pptx.d.ts +0 -49
  66. package/dist/theme07-pptx.d.ts.map +0 -1
  67. package/dist/theme07-pptx.js +0 -2579
  68. package/dist/theme07-pptx.js.map +0 -1
  69. package/dist/theme08-pptx.d.ts +0 -19
  70. package/dist/theme08-pptx.d.ts.map +0 -1
  71. package/dist/theme08-pptx.js +0 -1411
  72. package/dist/theme08-pptx.js.map +0 -1
  73. package/dist/theme09-pptx.d.ts +0 -18
  74. package/dist/theme09-pptx.d.ts.map +0 -1
  75. package/dist/theme09-pptx.js +0 -1513
  76. package/dist/theme09-pptx.js.map +0 -1
  77. package/dist/theme10-pptx.d.ts +0 -18
  78. package/dist/theme10-pptx.d.ts.map +0 -1
  79. package/dist/theme10-pptx.js +0 -2889
  80. package/dist/theme10-pptx.js.map +0 -1
  81. package/dist/theme11-pptx.d.ts +0 -17
  82. package/dist/theme11-pptx.d.ts.map +0 -1
  83. package/dist/theme11-pptx.js +0 -908
  84. package/dist/theme11-pptx.js.map +0 -1
@@ -1,908 +0,0 @@
1
- // lemonPPT - AI-powered presentation generation
2
- // Copyright (c) 2026 lemonforme
3
- // SPDX-License-Identifier: AGPL-3.0-or-later
4
- //
5
- // theme11 流光科技 · 浅色扁平科技风 — PPTX 版式渲染器。
6
- // 采用「按角色通用渲染」策略:所有 theme11 版式共享同一渲染入口,
7
- // 按布局 ID 与 props 字段自动分发封面/章节/内容/数据/图表/影像/结束页,
8
- // 避免导出时落到 "Unknown layout" 占位。
9
- let S = {
10
- appearance: 'primary',
11
- fonts: { heading: 'Noto Sans SC', body: 'Noto Sans SC', mono: 'Space Mono' },
12
- chartColors: ['00BCD4', '7C4DFF', '2979FF', 'FF9100', '00C853', 'FF5252'],
13
- };
14
- /** 每次导出开始时由 export-pptx.ts 注入 appearance / 字体 / 图表色。 */
15
- export function configureTheme11(state) {
16
- S = state;
17
- }
18
- // ────────────────────────────────────────────────────────────────
19
- // 情绪基底(PPTX 不支持复杂弥散渐变,取情绪主色作为页面底色)
20
- // ────────────────────────────────────────────────────────────────
21
- const MOOD_BG = {
22
- aurora: 'E0F7FA',
23
- daylight: 'F8FAFC',
24
- sunset: 'FFF8E1',
25
- };
26
- const PALETTE = {
27
- primary: {
28
- ink: '1A202C',
29
- ink2: '5A6578',
30
- ink3: '8B95A5',
31
- accent: '00BCD4',
32
- accent2: '7C4DFF',
33
- accent3: '2979FF',
34
- orange: 'FF9100',
35
- red: 'FF5252',
36
- green: '00C853',
37
- surface: 'FFFFFF',
38
- border: 'E2E8F0',
39
- },
40
- muted: {
41
- ink: '4A5568',
42
- ink2: '718096',
43
- ink3: 'A0AEC0',
44
- accent: '4DD0E1',
45
- accent2: '9575CD',
46
- accent3: '5C9CFF',
47
- orange: 'FFB74D',
48
- red: 'FF8A80',
49
- green: '69F0AE',
50
- surface: 'FFFFFF',
51
- border: 'E2E8F0',
52
- },
53
- };
54
- function pal() {
55
- return PALETTE[S.appearance];
56
- }
57
- // ────────────────────────────────────────────────────────────────
58
- // 小工具
59
- // ────────────────────────────────────────────────────────────────
60
- function applyBackground(slide, mood) {
61
- slide.background = { color: MOOD_BG[mood] };
62
- }
63
- function fitText(text) {
64
- if (text === undefined || text === null)
65
- return '';
66
- return String(text);
67
- }
68
- function parseValues(values) {
69
- if (Array.isArray(values)) {
70
- return values
71
- .map((v) => (typeof v === 'number' ? v : Number.parseFloat(String(v).replace(/[, %]/g, ''))))
72
- .filter((v) => Number.isFinite(v));
73
- }
74
- if (typeof values === 'string' && values.length > 0) {
75
- return values
76
- .split(/[,,]/)
77
- .map((s) => Number.parseFloat(s.trim().replace(/[%\s]/g, '')))
78
- .filter((v) => Number.isFinite(v));
79
- }
80
- return [];
81
- }
82
- function normalizeCategories(cats) {
83
- if (!cats)
84
- return [];
85
- if (Array.isArray(cats)) {
86
- return cats.map((c) => (typeof c === 'string' ? c : fitText(c.text)));
87
- }
88
- return [];
89
- }
90
- function getToneColor(tone) {
91
- const p = pal();
92
- switch (tone) {
93
- case 'violet':
94
- case 'purple':
95
- return p.accent2;
96
- case 'blue':
97
- return p.accent3;
98
- case 'orange':
99
- return p.orange;
100
- case 'green':
101
- return p.green;
102
- case 'red':
103
- return p.red;
104
- case 'accent':
105
- default:
106
- return p.accent;
107
- }
108
- }
109
- function getDefaultMood(layoutId) {
110
- const id = layoutId.toLowerCase();
111
- if (id.includes('sunset') || id.includes('closing_cta') || id.includes('risk') || id.includes('pyramid'))
112
- return 'sunset';
113
- if (id.includes('aurora') || id.includes('cover') || id.includes('divider') || id.includes('statement'))
114
- return 'aurora';
115
- return 'daylight';
116
- }
117
- // ────────────────────────────────────────────────────────────────
118
- // 基础排版元素
119
- // ────────────────────────────────────────────────────────────────
120
- function addEyebrow(slide, text, y = 0.85) {
121
- if (!text)
122
- return;
123
- slide.addText(text, {
124
- x: 0.6,
125
- y,
126
- w: 8.8,
127
- h: 0.35,
128
- fontSize: 12,
129
- fontFace: S.fonts.mono,
130
- color: pal().accent,
131
- bold: true,
132
- letterSpacing: 1,
133
- charSpacing: 1,
134
- });
135
- }
136
- function addTitle(slide, text, y = 1.25, opts) {
137
- if (!text)
138
- return;
139
- slide.addText(text, {
140
- x: opts?.x ?? 0.6,
141
- y,
142
- w: opts?.w ?? 8.8,
143
- h: 1.0,
144
- fontSize: opts?.fontSize ?? 36,
145
- fontFace: S.fonts.heading,
146
- color: pal().ink,
147
- bold: true,
148
- align: opts?.align ?? 'left',
149
- });
150
- }
151
- function addSubtitle(slide, text, y = 2.15, opts) {
152
- if (!text)
153
- return;
154
- slide.addText(text, {
155
- x: opts?.x ?? 0.6,
156
- y,
157
- w: opts?.w ?? 8.8,
158
- h: 0.7,
159
- fontSize: 18,
160
- fontFace: S.fonts.body,
161
- color: pal().ink2,
162
- align: opts?.align ?? 'left',
163
- });
164
- }
165
- function addBody(slide, text, y, h = 1.2) {
166
- if (!text)
167
- return;
168
- slide.addText(text, {
169
- x: 0.6,
170
- y,
171
- w: 8.8,
172
- h,
173
- fontSize: 15,
174
- fontFace: S.fonts.body,
175
- color: pal().ink,
176
- });
177
- }
178
- function addBulletList(slide, items, yStart, x = 0.6, w = 8.8) {
179
- const p = pal();
180
- let y = yStart;
181
- const list = Array.isArray(items) ? items : [];
182
- list.forEach((item) => {
183
- const text = typeof item === 'string' ? item : fitText(item.text);
184
- if (!text)
185
- return;
186
- slide.addText('●', { x, y, w: 0.25, h: 0.35, fontSize: 11, color: p.accent });
187
- slide.addText(text, { x: x + 0.3, y, w: w - 0.3, h: 0.35, fontSize: 14, fontFace: S.fonts.body, color: p.ink });
188
- y += 0.42;
189
- });
190
- return y;
191
- }
192
- function isImageSrc(url) {
193
- return /^(https?:|data:|file:|\/|[A-Za-z]:\\)/.test(url) || /\.(png|jpg|jpeg|gif|webp|svg)(\?.*)?$/i.test(url);
194
- }
195
- function addImageFromProp(slide, url, x, y, w, h) {
196
- if (!url || !isImageSrc(url))
197
- return;
198
- try {
199
- slide.addImage({ path: url, x, y, w, h, sizing: { type: 'crop', w, h } });
200
- }
201
- catch {
202
- slide.addText('[图片]', { x, y, w, h, fontSize: 14, color: pal().ink2, align: 'center', valign: 'middle' });
203
- }
204
- }
205
- // ────────────────────────────────────────────────────────────────
206
- // 图表渲染(抽取常见字段,复杂图表降级为文本占位)
207
- // ────────────────────────────────────────────────────────────────
208
- function inferChartType(layoutId) {
209
- if (layoutId.includes('hbar'))
210
- return 'bar';
211
- if (layoutId.includes('line'))
212
- return 'line';
213
- if (layoutId.includes('area'))
214
- return 'line';
215
- if (layoutId.includes('pie'))
216
- return 'pie';
217
- if (layoutId.includes('donut'))
218
- return 'doughnut';
219
- if (layoutId.includes('radar'))
220
- return 'radar';
221
- if (layoutId.includes('scatter'))
222
- return 'scatter';
223
- if (layoutId.includes('bubble'))
224
- return 'bubble';
225
- return 'bar';
226
- }
227
- function renderChart(slide, props, layoutId) {
228
- const p = props;
229
- const categories = normalizeCategories(p.categories ?? p.labels ?? p.xAxis);
230
- const values = parseValues(p.values ?? p.data ?? p.series);
231
- const title = fitText(p.title);
232
- const subtitle = fitText(p.subtitle);
233
- // 复杂/ unsupported 图表直接降级
234
- const unsupported = /treemap|sunburst|sankey|graph|network|parallel|funnel|gauge|candlestick|heatmap|bump|radian|waterfall|quadrant|matrix|venn|orgchart|gantt|calendar|small_multiples|swimlane/.test(layoutId);
235
- let y = 1.35;
236
- if (title) {
237
- addTitle(slide, title, y);
238
- y += 0.85;
239
- }
240
- if (subtitle) {
241
- addSubtitle(slide, subtitle, y);
242
- y += 0.55;
243
- }
244
- if (unsupported || categories.length === 0 || values.length === 0) {
245
- slide.addShape('roundRect', {
246
- x: 0.8,
247
- y,
248
- w: 8.4,
249
- h: 3.6,
250
- fill: { color: pal().surface },
251
- line: { color: pal().border, width: 1 },
252
- rectRadius: 0.15,
253
- });
254
- slide.addText('(图表数据将在导出后由编辑器渲染)', {
255
- x: 0.8,
256
- y,
257
- w: 8.4,
258
- h: 3.6,
259
- fontSize: 16,
260
- color: pal().ink2,
261
- align: 'center',
262
- valign: 'middle',
263
- fontFace: S.fonts.body,
264
- });
265
- return true;
266
- }
267
- const type = inferChartType(layoutId);
268
- const chartColors = type === 'pie' || type === 'doughnut'
269
- ? categories.map((_, i) => S.chartColors[i % S.chartColors.length])
270
- : [S.chartColors[0]];
271
- const data = [{ name: title || '数据', labels: categories.slice(0, values.length), values }];
272
- slide.addChart(type, data, {
273
- x: 0.7,
274
- y,
275
- w: 8.6,
276
- h: 3.6,
277
- chartColors,
278
- showValue: true,
279
- dataLabelColor: pal().ink,
280
- dataLabelFontSize: 10,
281
- });
282
- return true;
283
- }
284
- // ────────────────────────────────────────────────────────────────
285
- // 数据表格
286
- // ────────────────────────────────────────────────────────────────
287
- function renderTable(slide, props) {
288
- const columns = props.columns || props.headers || [];
289
- const rows = props.rows || props.data || [];
290
- if (columns.length === 0 && rows.length === 0)
291
- return false;
292
- const headers = columns.map((c) => (typeof c === 'string' ? c : fitText(c.title ?? c.label ?? c.text)));
293
- const body = rows.map((row) => Array.isArray(row)
294
- ? row.map((cell) => (typeof cell === 'string' ? cell : fitText(cell.text ?? cell.value)))
295
- : []);
296
- if (headers.length === 0 && body.length === 0)
297
- return false;
298
- const tableData = [headers, ...body].filter((r) => r.length > 0);
299
- if (tableData.length === 0)
300
- return false;
301
- slide.addTable(tableData, {
302
- x: 0.6,
303
- y: 2.3,
304
- w: 8.8,
305
- h: 3.2,
306
- fontSize: 12,
307
- fontFace: S.fonts.body,
308
- color: pal().ink,
309
- border: { type: 'solid', pt: 0.5, color: pal().border },
310
- fill: { color: pal().surface },
311
- colW: headers.map(() => 8.8 / Math.max(headers.length, 1)),
312
- });
313
- return true;
314
- }
315
- // ────────────────────────────────────────────────────────────────
316
- // 通用卡片/指标网格
317
- // ────────────────────────────────────────────────────────────────
318
- function renderItemCards(slide, items, yStart, columns = 4) {
319
- const list = Array.isArray(items) ? items.slice(0, 6) : [];
320
- if (list.length === 0)
321
- return yStart;
322
- const p = pal();
323
- const colCount = Math.min(list.length, columns);
324
- const gap = 0.25;
325
- const cardW = (8.8 - gap * (colCount - 1)) / colCount;
326
- let y = yStart;
327
- list.forEach((item, i) => {
328
- const row = Math.floor(i / colCount);
329
- const col = i % colCount;
330
- const x = 0.6 + col * (cardW + gap);
331
- const cy = y + row * 1.45;
332
- const value = fitText(item.value ?? item.metric ?? item.number);
333
- const label = fitText(item.label ?? item.title ?? item.name);
334
- const change = fitText(item.change ?? item.delta);
335
- const tone = fitText(item.tone);
336
- const color = getToneColor(tone || (i % 2 === 0 ? 'accent' : 'violet'));
337
- slide.addShape('roundRect', {
338
- x,
339
- y: cy,
340
- w: cardW,
341
- h: 1.25,
342
- fill: { color: p.surface },
343
- line: { color: p.border, width: 1 },
344
- rectRadius: 0.12,
345
- });
346
- if (value) {
347
- slide.addText(value, { x: x + 0.12, y: cy + 0.12, w: cardW - 0.24, h: 0.45, fontSize: 24, fontFace: S.fonts.heading, bold: true, color });
348
- }
349
- if (change) {
350
- slide.addText(change, { x: x + cardW - 0.9, y: cy + 0.16, w: 0.78, h: 0.35, fontSize: 11, fontFace: S.fonts.mono, color: p.ink2, align: 'right' });
351
- }
352
- if (label) {
353
- slide.addText(label, { x: x + 0.12, y: cy + 0.58, w: cardW - 0.24, h: 0.45, fontSize: 12, fontFace: S.fonts.body, color: p.ink2 });
354
- }
355
- });
356
- return y + Math.ceil(list.length / colCount) * 1.45 + 0.2;
357
- }
358
- function renderFeatureCards(slide, items, yStart, columns = 3) {
359
- const list = Array.isArray(items) ? items.slice(0, 6) : [];
360
- if (list.length === 0)
361
- return yStart;
362
- const p = pal();
363
- const colCount = Math.min(list.length, columns);
364
- const gap = 0.25;
365
- const cardW = (8.8 - gap * (colCount - 1)) / colCount;
366
- let y = yStart;
367
- list.forEach((item, i) => {
368
- const row = Math.floor(i / colCount);
369
- const col = i % colCount;
370
- const x = 0.6 + col * (cardW + gap);
371
- const cy = y + row * 1.65;
372
- const title = fitText(item.title ?? item.label);
373
- const desc = fitText(item.desc ?? item.description ?? item.text);
374
- const icon = fitText(item.icon);
375
- const tone = fitText(item.tone);
376
- const color = getToneColor(tone || 'accent');
377
- slide.addShape('roundRect', {
378
- x,
379
- y: cy,
380
- w: cardW,
381
- h: 1.45,
382
- fill: { color: p.surface },
383
- line: { color: p.border, width: 1 },
384
- rectRadius: 0.12,
385
- });
386
- if (icon) {
387
- slide.addShape('roundRect', { x: x + 0.12, y: cy + 0.12, w: 0.38, h: 0.38, fill: { color: color }, rectRadius: 0.08 });
388
- slide.addText(icon, { x: x + 0.12, y: cy + 0.12, w: 0.38, h: 0.38, fontSize: 14, color: 'FFFFFF', align: 'center', valign: 'middle' });
389
- }
390
- if (title) {
391
- slide.addText(title, { x: x + 0.12, y: cy + 0.55, w: cardW - 0.24, h: 0.35, fontSize: 14, fontFace: S.fonts.heading, bold: true, color: p.ink });
392
- }
393
- if (desc) {
394
- slide.addText(desc, { x: x + 0.12, y: cy + 0.9, w: cardW - 0.24, h: 0.45, fontSize: 10, fontFace: S.fonts.body, color: p.ink3 });
395
- }
396
- });
397
- return y + Math.ceil(list.length / colCount) * 1.65 + 0.2;
398
- }
399
- function renderComparisonCards(slide, props) {
400
- const p = pal();
401
- const leftTitle = fitText(props.leftTitle ?? '方案 A');
402
- const rightTitle = fitText(props.rightTitle ?? '方案 B');
403
- const leftItems = (props.leftItems ?? props.leftPoints ?? []);
404
- const rightItems = (props.rightItems ?? props.rightPoints ?? []);
405
- slide.addShape('roundRect', { x: 0.6, y: 2.5, w: 4.1, h: 3.0, fill: { color: p.surface }, line: { color: p.border }, rectRadius: 0.12 });
406
- slide.addShape('roundRect', { x: 5.3, y: 2.5, w: 4.1, h: 3.0, fill: { color: p.surface }, line: { color: p.border }, rectRadius: 0.12 });
407
- slide.addText(leftTitle, { x: 0.8, y: 2.65, w: 3.7, h: 0.4, fontSize: 18, fontFace: S.fonts.heading, bold: true, color: p.accent });
408
- slide.addText(rightTitle, { x: 5.5, y: 2.65, w: 3.7, h: 0.4, fontSize: 18, fontFace: S.fonts.heading, bold: true, color: p.accent2 });
409
- addBulletList(slide, leftItems, 3.15, 0.85, 3.6);
410
- addBulletList(slide, rightItems, 3.15, 5.55, 3.6);
411
- }
412
- // ────────────────────────────────────────────────────────────────
413
- // 影像/画廊
414
- // ────────────────────────────────────────────────────────────────
415
- function renderGallery(slide, props) {
416
- const images = (props.images ?? props.items ?? []);
417
- if (images.length === 0 && props.image) {
418
- addImageFromProp(slide, fitText(props.image), 0, 0, 10, 5.625);
419
- return;
420
- }
421
- const cols = Math.min(images.length, 3);
422
- const gap = 0.2;
423
- const imgW = (8.8 - gap * (cols - 1)) / cols;
424
- const imgH = 2.4;
425
- images.slice(0, 6).forEach((img, i) => {
426
- const row = Math.floor(i / cols);
427
- const col = i % cols;
428
- const url = typeof img === 'string' ? img : fitText(img.src ?? img.image ?? img.url);
429
- addImageFromProp(slide, url, 0.6 + col * (imgW + gap), 2.3 + row * (imgH + gap), imgW, imgH);
430
- });
431
- }
432
- function renderPhotoSplit(slide, props) {
433
- const image = fitText(props.image);
434
- if (image) {
435
- addImageFromProp(slide, image, 5.0, 0, 5.0, 5.625);
436
- }
437
- const p = pal();
438
- slide.addShape('rect', { x: 0, y: 0, w: 5.0, h: 5.625, fill: { color: p.surface } });
439
- addTitle(slide, fitText(props.title), 1.8, { x: 0.6, w: 4.0, fontSize: 32 });
440
- addSubtitle(slide, fitText(props.subtitle), 2.7, { x: 0.6, w: 4.0 });
441
- }
442
- function renderTeam(slide, props) {
443
- const members = (props.members ?? props.items ?? []);
444
- renderMemberCards(slide, members, 2.4);
445
- }
446
- function renderMemberCards(slide, members, yStart) {
447
- const list = Array.isArray(members) ? members.slice(0, 6) : [];
448
- if (list.length === 0)
449
- return yStart;
450
- const p = pal();
451
- const cols = Math.min(list.length, 4);
452
- const gap = 0.25;
453
- const cardW = (8.8 - gap * (cols - 1)) / cols;
454
- let y = yStart;
455
- list.forEach((m, i) => {
456
- const row = Math.floor(i / cols);
457
- const col = i % cols;
458
- const x = 0.6 + col * (cardW + gap);
459
- const cy = y + row * 1.75;
460
- const name = fitText(m.name ?? m.title);
461
- const role = fitText(m.role ?? m.label);
462
- const avatar = fitText(m.avatar ?? m.image);
463
- slide.addShape('roundRect', { x, y: cy, w: cardW, h: 1.55, fill: { color: p.surface }, line: { color: p.border }, rectRadius: 0.12 });
464
- if (avatar) {
465
- addImageFromProp(slide, avatar, x + cardW / 2 - 0.32, cy + 0.12, 0.64, 0.64);
466
- }
467
- else {
468
- slide.addShape('ellipse', { x: x + cardW / 2 - 0.32, y: cy + 0.12, w: 0.64, h: 0.64, fill: { color: p.border } });
469
- }
470
- if (name)
471
- slide.addText(name, { x, y: cy + 0.82, w: cardW, h: 0.35, fontSize: 13, fontFace: S.fonts.heading, bold: true, color: p.ink, align: 'center' });
472
- if (role)
473
- slide.addText(role, { x, y: cy + 1.12, w: cardW, h: 0.3, fontSize: 10, fontFace: S.fonts.body, color: p.ink2, align: 'center' });
474
- });
475
- return y + Math.ceil(list.length / cols) * 1.75 + 0.2;
476
- }
477
- // ────────────────────────────────────────────────────────────────
478
- // 流程/时间线/步骤
479
- // ────────────────────────────────────────────────────────────────
480
- function renderTimeline(slide, items, yStart) {
481
- const list = Array.isArray(items) ? items.slice(0, 6) : [];
482
- if (list.length === 0)
483
- return yStart;
484
- const p = pal();
485
- const gap = 8.8 / list.length;
486
- let y = yStart;
487
- list.forEach((item, i) => {
488
- const x = 0.6 + i * gap + gap / 2 - 0.2;
489
- const title = fitText(item.label ?? item.title ?? item.phase);
490
- const desc = fitText(item.desc ?? item.description);
491
- const number = fitText(item.number ?? String(i + 1));
492
- slide.addShape('ellipse', { x, y, w: 0.4, h: 0.4, fill: { color: p.accent } });
493
- slide.addText(number, { x, y, w: 0.4, h: 0.4, fontSize: 11, color: 'FFFFFF', align: 'center', valign: 'middle', bold: true });
494
- if (i < list.length - 1) {
495
- slide.addShape('line', { x1: x + 0.4, y1: y + 0.2, x2: x + gap - 0.4, y2: y + 0.2, line: { color: p.border, width: 2 } });
496
- }
497
- if (title)
498
- slide.addText(title, { x: x - 0.8, y: y + 0.55, w: 2.0, h: 0.35, fontSize: 12, fontFace: S.fonts.heading, bold: true, color: p.ink, align: 'center' });
499
- if (desc)
500
- slide.addText(desc, { x: x - 0.8, y: y + 0.95, w: 2.0, h: 0.7, fontSize: 10, fontFace: S.fonts.body, color: p.ink2, align: 'center' });
501
- });
502
- return y + 1.8;
503
- }
504
- function renderSteps(slide, items, yStart) {
505
- const list = Array.isArray(items) ? items.slice(0, 5) : [];
506
- if (list.length === 0)
507
- return yStart;
508
- const p = pal();
509
- let y = yStart;
510
- list.forEach((item, i) => {
511
- const title = fitText(item.title ?? item.label);
512
- const desc = fitText(item.desc ?? item.description);
513
- slide.addShape('roundRect', { x: 0.6, y, w: 0.4, h: 0.4, fill: { color: p.accent }, rectRadius: 0.08 });
514
- slide.addText(String(i + 1), { x: 0.6, y, w: 0.4, h: 0.4, fontSize: 14, color: 'FFFFFF', align: 'center', valign: 'middle', bold: true });
515
- if (title)
516
- slide.addText(title, { x: 1.15, y, w: 8.2, h: 0.35, fontSize: 15, fontFace: S.fonts.heading, bold: true, color: p.ink });
517
- if (desc)
518
- slide.addText(desc, { x: 1.15, y: y + 0.35, w: 8.2, h: 0.35, fontSize: 12, fontFace: S.fonts.body, color: p.ink2 });
519
- y += 0.9;
520
- });
521
- return y;
522
- }
523
- // ────────────────────────────────────────────────────────────────
524
- // 定价/方案
525
- // ────────────────────────────────────────────────────────────────
526
- function renderPricing(slide, props) {
527
- const plans = (props.plans ?? props.items ?? props.cards ?? []);
528
- const list = Array.isArray(plans) ? plans.slice(0, 3) : [];
529
- if (list.length === 0)
530
- return;
531
- const p = pal();
532
- const cols = list.length;
533
- const gap = 0.25;
534
- const cardW = (8.8 - gap * (cols - 1)) / cols;
535
- const y = 2.4;
536
- list.forEach((plan, i) => {
537
- const x = 0.6 + i * (cardW + gap);
538
- const name = fitText(plan.name ?? plan.title);
539
- const price = fitText(plan.price ?? plan.value);
540
- const features = (plan.features ?? plan.items ?? []);
541
- const active = !!plan.active || !!plan.highlight;
542
- slide.addShape('roundRect', {
543
- x,
544
- y,
545
- w: cardW,
546
- h: 3.2,
547
- fill: { color: active ? p.accent : p.surface },
548
- line: { color: active ? p.accent : p.border, width: 1.5 },
549
- rectRadius: 0.15,
550
- });
551
- const textColor = active ? 'FFFFFF' : p.ink;
552
- if (name)
553
- slide.addText(name, { x, y: y + 0.2, w: cardW, h: 0.4, fontSize: 16, fontFace: S.fonts.heading, bold: true, color: textColor, align: 'center' });
554
- if (price)
555
- slide.addText(price, { x, y: y + 0.65, w: cardW, h: 0.5, fontSize: 28, fontFace: S.fonts.heading, bold: true, color: active ? 'FFFFFF' : p.accent, align: 'center' });
556
- features.slice(0, 4).forEach((f, idx) => {
557
- const text = typeof f === 'string' ? f : fitText(f.text);
558
- slide.addText(text, { x: x + 0.15, y: y + 1.25 + idx * 0.35, w: cardW - 0.3, h: 0.32, fontSize: 11, fontFace: S.fonts.body, color: textColor, align: 'center' });
559
- });
560
- });
561
- }
562
- // ────────────────────────────────────────────────────────────────
563
- // FAQ / 检查清单 / 引用
564
- // ────────────────────────────────────────────────────────────────
565
- function renderFaq(slide, props) {
566
- const items = (props.items ?? props.qa ?? []);
567
- const p = pal();
568
- let y = 2.3;
569
- items.slice(0, 4).forEach((item) => {
570
- const q = fitText(item.question ?? item.q ?? item.title);
571
- const a = fitText(item.answer ?? item.a ?? item.desc);
572
- slide.addShape('roundRect', { x: 0.6, y, w: 8.8, h: 0.7, fill: { color: p.surface }, line: { color: p.border }, rectRadius: 0.1 });
573
- slide.addText(q, { x: 0.8, y: y + 0.1, w: 8.4, h: 0.28, fontSize: 13, fontFace: S.fonts.heading, bold: true, color: p.ink });
574
- slide.addText(a, { x: 0.8, y: y + 0.36, w: 8.4, h: 0.28, fontSize: 11, fontFace: S.fonts.body, color: p.ink2 });
575
- y += 0.82;
576
- });
577
- }
578
- function renderQuote(slide, props) {
579
- const quote = fitText(props.quote ?? props.text ?? props.statement);
580
- const author = fitText(props.author ?? props.name ?? props.source);
581
- const p = pal();
582
- if (quote) {
583
- slide.addText('“', { x: 0.6, y: 1.9, w: 0.6, h: 0.6, fontSize: 48, fontFace: S.fonts.heading, color: p.accent });
584
- slide.addText(quote, { x: 1.2, y: 2.1, w: 7.8, h: 1.6, fontSize: 24, fontFace: S.fonts.heading, color: p.ink, italic: true });
585
- }
586
- if (author) {
587
- slide.addText(`— ${author}`, { x: 1.2, y: 3.8, w: 7.8, h: 0.4, fontSize: 14, fontFace: S.fonts.body, color: p.ink2 });
588
- }
589
- }
590
- // ────────────────────────────────────────────────────────────────
591
- // 主入口:按角色分发
592
- // ────────────────────────────────────────────────────────────────
593
- export function renderTheme11Slide(slide, props, layoutId) {
594
- const p = props;
595
- const mood = p.mood || getDefaultMood(layoutId);
596
- applyBackground(slide, mood);
597
- const id = layoutId.toLowerCase();
598
- const title = fitText(p.title);
599
- const subtitle = fitText(p.subtitle);
600
- const eyebrow = fitText(p.eyebrow ?? p.tagline ?? p.kicker ?? p.label);
601
- // 封面
602
- if (id.includes('cover')) {
603
- if (p.image) {
604
- addImageFromProp(slide, fitText(p.image), 0, 0, 10, 5.625);
605
- slide.addShape('rect', { x: 0, y: 0, w: 10, h: 5.625, fill: { color: 'FFFFFF', transparency: 70 } });
606
- }
607
- if (eyebrow)
608
- addEyebrow(slide, eyebrow, 1.3);
609
- addTitle(slide, title, 1.8, { align: 'center', x: 0.6, w: 8.8, fontSize: 44 });
610
- addSubtitle(slide, subtitle, 2.9, { align: 'center', x: 1, w: 8 });
611
- if (p.date) {
612
- slide.addText(fitText(p.date), { x: 0.6, y: 4.9, w: 8.8, h: 0.3, fontSize: 12, fontFace: S.fonts.mono, color: pal().ink3, align: 'center' });
613
- }
614
- return;
615
- }
616
- // 章节 / 分隔 / 金句 / 过渡
617
- if (id.includes('chapter') || id.includes('divider') || id.includes('statement') || id.includes('transition')) {
618
- if (eyebrow)
619
- addEyebrow(slide, eyebrow, 1.8);
620
- if (p.number || p.keyword) {
621
- slide.addText(fitText(p.number ?? p.keyword), {
622
- x: 0.6,
623
- y: 2.0,
624
- w: 8.8,
625
- h: 1.0,
626
- fontSize: 72,
627
- fontFace: S.fonts.heading,
628
- bold: true,
629
- color: pal().accent,
630
- align: 'center',
631
- });
632
- addTitle(slide, title, 3.1, { align: 'center', x: 0.6, w: 8.8, fontSize: 32 });
633
- }
634
- else {
635
- addTitle(slide, title, 2.4, { align: 'center', x: 0.6, w: 8.8, fontSize: 40 });
636
- }
637
- addSubtitle(slide, subtitle, 3.4, { align: 'center', x: 1, w: 8 });
638
- if (p.image) {
639
- addImageFromProp(slide, fitText(p.image), 2, 3.8, 6, 1.4);
640
- }
641
- return;
642
- }
643
- // 结束页
644
- if (id.includes('closing') || id.includes('back_cover')) {
645
- addTitle(slide, title, 1.8, { align: 'center', x: 0.6, w: 8.8, fontSize: 42 });
646
- addSubtitle(slide, subtitle, 2.8, { align: 'center', x: 1, w: 8 });
647
- if (p.cta) {
648
- slide.addShape('roundRect', { x: 3.5, y: 3.6, w: 3, h: 0.6, fill: { color: pal().accent }, rectRadius: 0.3 });
649
- slide.addText(fitText(p.cta), { x: 3.5, y: 3.6, w: 3, h: 0.6, fontSize: 16, color: 'FFFFFF', bold: true, align: 'center', valign: 'middle' });
650
- }
651
- const contacts = [p.contact, p.email, p.link, p.phone, p.website].filter(Boolean).map(fitText);
652
- let cy = 4.5;
653
- contacts.forEach((c) => {
654
- slide.addText(c, { x: 0.6, y: cy, w: 8.8, h: 0.3, fontSize: 13, fontFace: S.fonts.mono, color: pal().accent2, align: 'center' });
655
- cy += 0.38;
656
- });
657
- return;
658
- }
659
- // 通用头部
660
- let cursorY = 1.25;
661
- if (eyebrow) {
662
- addEyebrow(slide, eyebrow, cursorY);
663
- cursorY += 0.45;
664
- }
665
- if (title) {
666
- addTitle(slide, title, cursorY);
667
- cursorY += 0.9;
668
- }
669
- if (subtitle) {
670
- addSubtitle(slide, subtitle, cursorY);
671
- cursorY += 0.6;
672
- }
673
- // 图表
674
- if (id.includes('chart') || id.includes('quadrant') || id.includes('matrix')) {
675
- renderChart(slide, p, layoutId);
676
- return;
677
- }
678
- // 表格
679
- if (id.includes('table')) {
680
- renderTable(slide, p);
681
- return;
682
- }
683
- // 影像与分屏
684
- if (id.includes('photo_split')) {
685
- renderPhotoSplit(slide, p);
686
- return;
687
- }
688
- if (id.includes('photo_feature') && p.image) {
689
- addImageFromProp(slide, fitText(p.image), 0, 0, 10, 5.625);
690
- slide.addShape('rect', { x: 0, y: 4.0, w: 10, h: 1.625, fill: { color: 'FFFFFF', transparency: 80 } });
691
- addTitle(slide, title, 4.15, { x: 0.6, w: 8.8, fontSize: 28 });
692
- return;
693
- }
694
- // 画廊
695
- if (id.includes('gallery') || id.includes('showcase')) {
696
- renderGallery(slide, p);
697
- return;
698
- }
699
- // 团队
700
- if (id.includes('team')) {
701
- renderTeam(slide, p);
702
- return;
703
- }
704
- // 引用/证言
705
- if (id.includes('quote') || id.includes('testimonial')) {
706
- if (id.includes('portrait') && p.image) {
707
- addImageFromProp(slide, fitText(p.image), 0.6, 2.2, 2.5, 2.5);
708
- renderQuote(slide, { ...p, quote: p.quote ?? p.text });
709
- }
710
- else {
711
- renderQuote(slide, p);
712
- }
713
- return;
714
- }
715
- // 定价/方案
716
- if (id.includes('pricing') || id.includes('plans')) {
717
- renderPricing(slide, p);
718
- return;
719
- }
720
- // 对比
721
- if (id.includes('comparison') || id.includes('swot') || id.includes('pest')) {
722
- renderComparisonCards(slide, p);
723
- return;
724
- }
725
- // FAQ
726
- if (id.includes('faq')) {
727
- renderFaq(slide, p);
728
- return;
729
- }
730
- // 检查清单
731
- if (id.includes('checklist')) {
732
- const items = (p.items ?? p.checklist ?? []);
733
- addBulletList(slide, items, cursorY);
734
- return;
735
- }
736
- // 时间线/路线图/流程/步骤
737
- if (id.includes('timeline') || id.includes('roadmap')) {
738
- renderTimeline(slide, (p.items ?? p.chapters ?? p.milestones ?? []), cursorY + 0.2);
739
- return;
740
- }
741
- if (id.includes('process') || id.includes('steps') || id.includes('cycle')) {
742
- renderSteps(slide, (p.items ?? p.steps ?? p.phases ?? []), cursorY + 0.2);
743
- return;
744
- }
745
- // 指标/数据
746
- if (id.includes('metric') || id.includes('stats') || id.includes('kpi') || id.includes('scorecard') || id.includes('index_board') || id.includes('progress') || id.includes('ranking') || id.includes('hero_number') || id.includes('big_number')) {
747
- if (p.value || p.number || p.metric) {
748
- slide.addText(fitText(p.value ?? p.number ?? p.metric), {
749
- x: 0.6,
750
- y: cursorY,
751
- w: 8.8,
752
- h: 1.0,
753
- fontSize: 56,
754
- fontFace: S.fonts.heading,
755
- bold: true,
756
- color: pal().accent,
757
- });
758
- cursorY += 1.0;
759
- }
760
- renderItemCards(slide, (p.items ?? p.metrics ?? p.data ?? []), cursorY + 0.2, 4);
761
- return;
762
- }
763
- // 特性卡片
764
- if (id.includes('feature') || id.includes('card') || id.includes('principles') || id.includes('glossary') || id.includes('case') || id.includes('editorial')) {
765
- renderFeatureCards(slide, (p.items ?? p.cards ?? p.features ?? p.terms ?? p.points ?? []), cursorY + 0.2, 3);
766
- return;
767
- }
768
- // 目录
769
- if (id.includes('contents') || id.includes('toc')) {
770
- const items = (p.items ?? p.sections ?? p.chapters ?? []);
771
- addBulletList(slide, items, cursorY);
772
- return;
773
- }
774
- // 通用内容兜底:body / bullets / image
775
- if (p.body) {
776
- addBody(slide, fitText(p.body), cursorY);
777
- }
778
- else if (p.points || p.items || p.bullets) {
779
- addBulletList(slide, (p.points ?? p.items ?? p.bullets), cursorY);
780
- }
781
- else if (p.image) {
782
- addImageFromProp(slide, fitText(p.image), 2.5, cursorY, 5, 2.8);
783
- }
784
- else if (!title && !subtitle) {
785
- slide.addText(`Theme 11 · ${layoutId}`, { x: 0.6, y: 2.5, w: 8.8, h: 0.5, fontSize: 14, color: pal().ink2, align: 'center' });
786
- }
787
- }
788
- // ────────────────────────────────────────────────────────────────
789
- // 注册全部 theme11 版式
790
- // ────────────────────────────────────────────────────────────────
791
- const THEME11_LAYOUTS = [
792
- 'theme11_back_cover_v1',
793
- 'theme11_bento_v1',
794
- 'theme11_big_number_v1',
795
- 'theme11_calendar_v1',
796
- 'theme11_case_study_v1',
797
- 'theme11_case_v1',
798
- 'theme11_chapter_cards_v1',
799
- 'theme11_chapter_hero_v1',
800
- 'theme11_chapter_numbered_v1',
801
- 'theme11_chapter_split_v1',
802
- 'theme11_chapter_timeline_v1',
803
- 'theme11_chapter_v1',
804
- 'theme11_chart_area_v1',
805
- 'theme11_chart_bar_v1',
806
- 'theme11_chart_bubble_v1',
807
- 'theme11_chart_bump_v1',
808
- 'theme11_chart_candlestick_v1',
809
- 'theme11_chart_donut_v1',
810
- 'theme11_chart_funnel_v1',
811
- 'theme11_chart_gauge_v1',
812
- 'theme11_chart_graph_v1',
813
- 'theme11_chart_grouped_v1',
814
- 'theme11_chart_hbar_v1',
815
- 'theme11_chart_heatmap_v1',
816
- 'theme11_chart_line_v1',
817
- 'theme11_chart_parallel_v1',
818
- 'theme11_chart_pie_v1',
819
- 'theme11_chart_radar_v1',
820
- 'theme11_chart_radian_v1',
821
- 'theme11_chart_sankey_v1',
822
- 'theme11_chart_scatter_v1',
823
- 'theme11_chart_stack_v1',
824
- 'theme11_chart_sunburst_v1',
825
- 'theme11_chart_treemap_v1',
826
- 'theme11_chart_waterfall_v1',
827
- 'theme11_checklist_v1',
828
- 'theme11_closing_contact_v1',
829
- 'theme11_closing_cta_v1',
830
- 'theme11_closing_minimal_v1',
831
- 'theme11_closing_quote_v1',
832
- 'theme11_closing_social_v1',
833
- 'theme11_closing_split_v1',
834
- 'theme11_closing_v1',
835
- 'theme11_comparison_cards_v1',
836
- 'theme11_comparison_v1',
837
- 'theme11_components_showcase_v1',
838
- 'theme11_contents_v1',
839
- 'theme11_cover_aurora_v1',
840
- 'theme11_cover_daylight_v1',
841
- 'theme11_cover_product_v1',
842
- 'theme11_cover_split_v1',
843
- 'theme11_cover_sunset_v1',
844
- 'theme11_cycle_v1',
845
- 'theme11_divider_v1',
846
- 'theme11_editorial_v1',
847
- 'theme11_faq_v1',
848
- 'theme11_feature_cards_v1',
849
- 'theme11_feature_grid_v1',
850
- 'theme11_feature_v1',
851
- 'theme11_gallery_v1',
852
- 'theme11_gallery_wall_v1',
853
- 'theme11_gantt_v1',
854
- 'theme11_glossary_v1',
855
- 'theme11_hero_number_v1',
856
- 'theme11_index_board_v1',
857
- 'theme11_kpi_strip_v1',
858
- 'theme11_matrix_v1',
859
- 'theme11_metric_big_v1',
860
- 'theme11_metrics_v1',
861
- 'theme11_network_v1',
862
- 'theme11_orgchart_v1',
863
- 'theme11_partners_v1',
864
- 'theme11_pest_v1',
865
- 'theme11_photo_feature_v1',
866
- 'theme11_photo_split_v1',
867
- 'theme11_plans_v1',
868
- 'theme11_pricing_v1',
869
- 'theme11_principles_v1',
870
- 'theme11_process_v1',
871
- 'theme11_progress_v1',
872
- 'theme11_pyramid_v1',
873
- 'theme11_quadrant_v1',
874
- 'theme11_quote_portrait_v1',
875
- 'theme11_quote_v1',
876
- 'theme11_ranking_v1',
877
- 'theme11_risk_v1',
878
- 'theme11_roadmap_v1',
879
- 'theme11_scorecard_v1',
880
- 'theme11_showcase_v1',
881
- 'theme11_small_multiples_v1',
882
- 'theme11_stat_strip_v1',
883
- 'theme11_statement_v1',
884
- 'theme11_steps_v1',
885
- 'theme11_summary_v1',
886
- 'theme11_swimlane_v1',
887
- 'theme11_swot_v1',
888
- 'theme11_table_data_v1',
889
- 'theme11_table_of_contents_v1',
890
- 'theme11_table_v1',
891
- 'theme11_team_v1',
892
- 'theme11_testimonial_v1',
893
- 'theme11_timeline_v1',
894
- 'theme11_transition_image_v1',
895
- 'theme11_transition_minimal_v1',
896
- 'theme11_trend_v1',
897
- 'theme11_venn_v1',
898
- ];
899
- const THEME11_RENDERERS = Object.fromEntries(THEME11_LAYOUTS.map((id) => [id, (slide, props) => renderTheme11Slide(slide, props, id)]));
900
- const RENDERERS = {
901
- ...THEME11_RENDERERS,
902
- };
903
- export function registerTheme11Renderers(register) {
904
- for (const [id, fn] of Object.entries(RENDERERS)) {
905
- register(id, fn);
906
- }
907
- }
908
- //# sourceMappingURL=theme11-pptx.js.map