@aalis/plugin-office 0.1.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.
- package/README.md +21 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/session.d.ts +24 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +38 -0
- package/dist/session.js.map +1 -0
- package/dist/tools/docx.d.ts +5 -0
- package/dist/tools/docx.d.ts.map +1 -0
- package/dist/tools/docx.js +640 -0
- package/dist/tools/docx.js.map +1 -0
- package/dist/tools/pdf.d.ts +6 -0
- package/dist/tools/pdf.d.ts.map +1 -0
- package/dist/tools/pdf.js +241 -0
- package/dist/tools/pdf.js.map +1 -0
- package/dist/tools/pptx.d.ts +5 -0
- package/dist/tools/pptx.d.ts.map +1 -0
- package/dist/tools/pptx.js +940 -0
- package/dist/tools/pptx.js.map +1 -0
- package/dist/tools/xlsx.d.ts +5 -0
- package/dist/tools/xlsx.d.ts.map +1 -0
- package/dist/tools/xlsx.js +509 -0
- package/dist/tools/xlsx.js.map +1 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +44 -0
- package/dist/utils.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,940 @@
|
|
|
1
|
+
import PptxGenJS from 'pptxgenjs';
|
|
2
|
+
import { loadImage } from '../utils.js';
|
|
3
|
+
/** 各 layout 对应的尺寸(英寸) */
|
|
4
|
+
const LAYOUT_DIMS = {
|
|
5
|
+
LAYOUT_16x9: [10, 5.63],
|
|
6
|
+
LAYOUT_4x3: [10, 7.5],
|
|
7
|
+
LAYOUT_WIDE: [13.33, 7.5],
|
|
8
|
+
};
|
|
9
|
+
/** 将元素位置/尺寸钳制在幻灯片可见区域内 */
|
|
10
|
+
function clampBounds(state, x, y, w, h, defaults) {
|
|
11
|
+
const d = defaults || { x: 0.5, y: 0.5, w: 9, h: 1 };
|
|
12
|
+
let ex = x != null ? Number(x) : d.x;
|
|
13
|
+
let ey = y != null ? Number(y) : d.y;
|
|
14
|
+
let ew = w != null ? Number(w) : d.w;
|
|
15
|
+
let eh = h != null ? Number(h) : d.h;
|
|
16
|
+
// 确保宽高不超过幻灯片尺寸
|
|
17
|
+
ew = Math.min(ew, state.slideW);
|
|
18
|
+
eh = Math.min(eh, state.slideH);
|
|
19
|
+
// 确保 x+w 不超出右边界
|
|
20
|
+
if (ex + ew > state.slideW)
|
|
21
|
+
ex = Math.max(0, state.slideW - ew);
|
|
22
|
+
// 确保 y+h 不超出下边界
|
|
23
|
+
if (ey + eh > state.slideH)
|
|
24
|
+
ey = Math.max(0, state.slideH - eh);
|
|
25
|
+
// 确保起点非负
|
|
26
|
+
ex = Math.max(0, ex);
|
|
27
|
+
ey = Math.max(0, ey);
|
|
28
|
+
return { x: ex, y: ey, w: ew, h: eh };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 根据颜色主题生成标准母版集(title / content / section / end)
|
|
32
|
+
* 所有坐标基于 16:9 (10×5.63"),创建时会自适应
|
|
33
|
+
*/
|
|
34
|
+
function buildTemplateMasters(c) {
|
|
35
|
+
return [
|
|
36
|
+
// 封面页
|
|
37
|
+
{
|
|
38
|
+
title: 'title',
|
|
39
|
+
background: { color: c.primary },
|
|
40
|
+
objects: [
|
|
41
|
+
{ rect: { x: 0, y: 4.4, w: 10, h: 1.23, fill: { color: c.secondary } } },
|
|
42
|
+
{
|
|
43
|
+
text: {
|
|
44
|
+
text: '',
|
|
45
|
+
options: {
|
|
46
|
+
x: 0.6,
|
|
47
|
+
y: 1.2,
|
|
48
|
+
w: 8.8,
|
|
49
|
+
h: 1.5,
|
|
50
|
+
color: c.background,
|
|
51
|
+
fontSize: 36,
|
|
52
|
+
bold: true,
|
|
53
|
+
placeholder: 'title',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
text: {
|
|
59
|
+
text: '',
|
|
60
|
+
options: { x: 0.6, y: 2.9, w: 8.8, h: 0.8, color: c.subtitle, fontSize: 18, placeholder: 'subtitle' },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
// 内容页
|
|
66
|
+
{
|
|
67
|
+
title: 'content',
|
|
68
|
+
background: { color: c.background },
|
|
69
|
+
objects: [
|
|
70
|
+
{ rect: { x: 0, y: 0, w: 10, h: 0.8, fill: { color: c.primary } } },
|
|
71
|
+
{
|
|
72
|
+
text: {
|
|
73
|
+
text: '',
|
|
74
|
+
options: {
|
|
75
|
+
x: 0.6,
|
|
76
|
+
y: 0.1,
|
|
77
|
+
w: 8.8,
|
|
78
|
+
h: 0.6,
|
|
79
|
+
color: c.background,
|
|
80
|
+
fontSize: 20,
|
|
81
|
+
bold: true,
|
|
82
|
+
placeholder: 'title',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
// 分节页(章节过渡)
|
|
89
|
+
{
|
|
90
|
+
title: 'section',
|
|
91
|
+
background: { color: c.secondary },
|
|
92
|
+
objects: [
|
|
93
|
+
{ rect: { x: 0.5, y: 2.0, w: 2, h: 0.06, fill: { color: c.background } } },
|
|
94
|
+
{
|
|
95
|
+
text: {
|
|
96
|
+
text: '',
|
|
97
|
+
options: {
|
|
98
|
+
x: 0.5,
|
|
99
|
+
y: 2.2,
|
|
100
|
+
w: 9,
|
|
101
|
+
h: 1.2,
|
|
102
|
+
color: c.background,
|
|
103
|
+
fontSize: 32,
|
|
104
|
+
bold: true,
|
|
105
|
+
placeholder: 'title',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
// 结束页
|
|
112
|
+
{
|
|
113
|
+
title: 'end',
|
|
114
|
+
background: { color: c.primary },
|
|
115
|
+
objects: [
|
|
116
|
+
{
|
|
117
|
+
text: {
|
|
118
|
+
text: 'Thank You',
|
|
119
|
+
options: { x: 0, y: 1.8, w: 10, h: 1.5, color: c.background, fontSize: 40, bold: true, align: 'center' },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
text: {
|
|
124
|
+
text: '',
|
|
125
|
+
options: {
|
|
126
|
+
x: 0,
|
|
127
|
+
y: 3.5,
|
|
128
|
+
w: 10,
|
|
129
|
+
h: 0.8,
|
|
130
|
+
color: c.subtitle,
|
|
131
|
+
fontSize: 16,
|
|
132
|
+
align: 'center',
|
|
133
|
+
placeholder: 'subtitle',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
const PRESET_TEMPLATES = {
|
|
142
|
+
clean: {
|
|
143
|
+
name: 'clean',
|
|
144
|
+
description: '简洁白色主题。白底配蓝色标题栏,适合商务汇报、技术分享、日常演示等通用场景。',
|
|
145
|
+
colors: {
|
|
146
|
+
primary: '2B579A',
|
|
147
|
+
secondary: '1A3A6B',
|
|
148
|
+
text: '333333',
|
|
149
|
+
background: 'FFFFFF',
|
|
150
|
+
title: 'FFFFFF',
|
|
151
|
+
subtitle: 'D0D8E8',
|
|
152
|
+
},
|
|
153
|
+
masters: buildTemplateMasters({
|
|
154
|
+
primary: '2B579A',
|
|
155
|
+
secondary: '1A3A6B',
|
|
156
|
+
text: '333333',
|
|
157
|
+
background: 'FFFFFF',
|
|
158
|
+
title: 'FFFFFF',
|
|
159
|
+
subtitle: 'D0D8E8',
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
dark: {
|
|
163
|
+
name: 'dark',
|
|
164
|
+
description: '深色科技主题。深蓝黑背景配亮色文字,适合科技产品发布、AI/数据、技术演讲等现代感场景。',
|
|
165
|
+
colors: {
|
|
166
|
+
primary: '0D1117',
|
|
167
|
+
secondary: '161B22',
|
|
168
|
+
text: 'E6EDF3',
|
|
169
|
+
background: '0D1117',
|
|
170
|
+
title: 'FFFFFF',
|
|
171
|
+
subtitle: '8B949E',
|
|
172
|
+
},
|
|
173
|
+
masters: buildTemplateMasters({
|
|
174
|
+
primary: '0D1117',
|
|
175
|
+
secondary: '161B22',
|
|
176
|
+
text: 'E6EDF3',
|
|
177
|
+
background: '0D1117',
|
|
178
|
+
title: 'FFFFFF',
|
|
179
|
+
subtitle: '8B949E',
|
|
180
|
+
}),
|
|
181
|
+
},
|
|
182
|
+
corporate: {
|
|
183
|
+
name: 'corporate',
|
|
184
|
+
description: '企业蓝色主题。经典蓝色调,沉稳专业,适合企业年报、战略规划、投资路演、正式汇报等商务场景。',
|
|
185
|
+
colors: {
|
|
186
|
+
primary: '1F4E79',
|
|
187
|
+
secondary: '2E75B6',
|
|
188
|
+
text: '333333',
|
|
189
|
+
background: 'FFFFFF',
|
|
190
|
+
title: 'FFFFFF',
|
|
191
|
+
subtitle: 'BDD7EE',
|
|
192
|
+
},
|
|
193
|
+
masters: buildTemplateMasters({
|
|
194
|
+
primary: '1F4E79',
|
|
195
|
+
secondary: '2E75B6',
|
|
196
|
+
text: '333333',
|
|
197
|
+
background: 'FFFFFF',
|
|
198
|
+
title: 'FFFFFF',
|
|
199
|
+
subtitle: 'BDD7EE',
|
|
200
|
+
}),
|
|
201
|
+
},
|
|
202
|
+
minimal: {
|
|
203
|
+
name: 'minimal',
|
|
204
|
+
description: '极简主题。纯白背景,黑色文字,几乎无装饰。适合学术报告、论文答辩、内容密集型演示。',
|
|
205
|
+
colors: {
|
|
206
|
+
primary: '222222',
|
|
207
|
+
secondary: '444444',
|
|
208
|
+
text: '222222',
|
|
209
|
+
background: 'FFFFFF',
|
|
210
|
+
title: 'FFFFFF',
|
|
211
|
+
subtitle: 'AAAAAA',
|
|
212
|
+
},
|
|
213
|
+
masters: buildTemplateMasters({
|
|
214
|
+
primary: '222222',
|
|
215
|
+
secondary: '444444',
|
|
216
|
+
text: '222222',
|
|
217
|
+
background: 'FFFFFF',
|
|
218
|
+
title: 'FFFFFF',
|
|
219
|
+
subtitle: 'AAAAAA',
|
|
220
|
+
}),
|
|
221
|
+
},
|
|
222
|
+
nature: {
|
|
223
|
+
name: 'nature',
|
|
224
|
+
description: '自然绿色主题。绿色调,清新自然,适合环保、农业、健康、教育等主题的演示。',
|
|
225
|
+
colors: {
|
|
226
|
+
primary: '2D6A4F',
|
|
227
|
+
secondary: '40916C',
|
|
228
|
+
text: '333333',
|
|
229
|
+
background: 'FFFFFF',
|
|
230
|
+
title: 'FFFFFF',
|
|
231
|
+
subtitle: 'B7E4C7',
|
|
232
|
+
},
|
|
233
|
+
masters: buildTemplateMasters({
|
|
234
|
+
primary: '2D6A4F',
|
|
235
|
+
secondary: '40916C',
|
|
236
|
+
text: '333333',
|
|
237
|
+
background: 'FFFFFF',
|
|
238
|
+
title: 'FFFFFF',
|
|
239
|
+
subtitle: 'B7E4C7',
|
|
240
|
+
}),
|
|
241
|
+
},
|
|
242
|
+
warm: {
|
|
243
|
+
name: 'warm',
|
|
244
|
+
description: '暖色橙红主题。温暖活泼,适合创意展示、市场营销、品牌推广、活动策划等需要活力感的场景。',
|
|
245
|
+
colors: {
|
|
246
|
+
primary: 'C0392B',
|
|
247
|
+
secondary: 'E74C3C',
|
|
248
|
+
text: '333333',
|
|
249
|
+
background: 'FFFFFF',
|
|
250
|
+
title: 'FFFFFF',
|
|
251
|
+
subtitle: 'FADBD8',
|
|
252
|
+
},
|
|
253
|
+
masters: buildTemplateMasters({
|
|
254
|
+
primary: 'C0392B',
|
|
255
|
+
secondary: 'E74C3C',
|
|
256
|
+
text: '333333',
|
|
257
|
+
background: 'FFFFFF',
|
|
258
|
+
title: 'FFFFFF',
|
|
259
|
+
subtitle: 'FADBD8',
|
|
260
|
+
}),
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
/** 把预设模板的母版注册到 pptx 实例上 */
|
|
264
|
+
function applyTemplate(pptx, templateName) {
|
|
265
|
+
const tpl = PRESET_TEMPLATES[templateName];
|
|
266
|
+
if (!tpl)
|
|
267
|
+
return null;
|
|
268
|
+
for (const master of tpl.masters) {
|
|
269
|
+
pptx.defineSlideMaster({
|
|
270
|
+
title: master.title,
|
|
271
|
+
background: master.background,
|
|
272
|
+
objects: master.objects,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return tpl;
|
|
276
|
+
}
|
|
277
|
+
export function registerPptTools(tools, sessions, storage, outputUri) {
|
|
278
|
+
function joinUri(base, rel) {
|
|
279
|
+
const b = base.endsWith('/') ? base : `${base}/`;
|
|
280
|
+
return `${b}${rel.replace(/^\/+/, '')}`;
|
|
281
|
+
}
|
|
282
|
+
function requireState(docId) {
|
|
283
|
+
return sessions.require(docId, 'pptx').doc;
|
|
284
|
+
}
|
|
285
|
+
function getSlide(state, slideNumber) {
|
|
286
|
+
const idx = (slideNumber ? slideNumber : state.slides.length) - 1;
|
|
287
|
+
if (idx < 0 || idx >= state.slides.length)
|
|
288
|
+
throw new Error(`幻灯片 ${idx + 1} 不存在`);
|
|
289
|
+
return state.slides[idx];
|
|
290
|
+
}
|
|
291
|
+
// ---- ppt_list_templates ----
|
|
292
|
+
tools.register({
|
|
293
|
+
definition: {
|
|
294
|
+
type: 'function',
|
|
295
|
+
function: {
|
|
296
|
+
name: 'ppt_list_templates',
|
|
297
|
+
description: '列出所有可用的 PPT 预设模板及其描述。创建 PPT 前调用此工具了解可选模板,帮助选择最适合演示主题的模板。',
|
|
298
|
+
parameters: { type: 'object', properties: {}, required: [] },
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
async handler() {
|
|
302
|
+
const list = Object.values(PRESET_TEMPLATES).map(t => ({
|
|
303
|
+
name: t.name,
|
|
304
|
+
description: t.description,
|
|
305
|
+
colors: { primary: t.colors.primary, text: t.colors.text, background: t.colors.background },
|
|
306
|
+
masters: t.masters.map(m => m.title),
|
|
307
|
+
}));
|
|
308
|
+
return JSON.stringify({
|
|
309
|
+
templates: list,
|
|
310
|
+
message: '创建 PPT 时通过 template 参数指定模板名称。模板会自动注册母版(title / content / section / end),添加幻灯片时通过 masterName 参数引用。',
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
// ---- ppt_create ----
|
|
315
|
+
tools.register({
|
|
316
|
+
definition: {
|
|
317
|
+
type: 'function',
|
|
318
|
+
function: {
|
|
319
|
+
name: 'ppt_create',
|
|
320
|
+
description: [
|
|
321
|
+
'创建一个新的 PowerPoint 演示文稿,返回 docId。',
|
|
322
|
+
'可通过 template 参数选择预设模板(如 clean/dark/corporate/minimal/nature/warm),模板会自动注册母版页。',
|
|
323
|
+
'使用模板后,添加幻灯片时用 masterName 引用母版:title(封面)、content(内容)、section(分节)、end(结束)。',
|
|
324
|
+
'该 docId 全局共享,可传递给子任务并行协作。',
|
|
325
|
+
].join('\n'),
|
|
326
|
+
parameters: {
|
|
327
|
+
type: 'object',
|
|
328
|
+
properties: {
|
|
329
|
+
filename: { type: 'string', description: '文件名(如 report.pptx)' },
|
|
330
|
+
title: { type: 'string', description: '演示文稿标题' },
|
|
331
|
+
author: { type: 'string', description: '作者' },
|
|
332
|
+
layout: {
|
|
333
|
+
type: 'string',
|
|
334
|
+
enum: ['LAYOUT_16x9', 'LAYOUT_4x3', 'LAYOUT_WIDE'],
|
|
335
|
+
description: '幻灯片比例,默认 LAYOUT_16x9',
|
|
336
|
+
},
|
|
337
|
+
template: {
|
|
338
|
+
type: 'string',
|
|
339
|
+
enum: ['clean', 'dark', 'corporate', 'minimal', 'nature', 'warm'],
|
|
340
|
+
description: '预设模板名称。选择后自动注册 title/content/section/end 四种母版页',
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
required: ['filename'],
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
async handler(args) {
|
|
348
|
+
const filename = String(args.filename || 'presentation.pptx');
|
|
349
|
+
const pptx = new PptxGenJS();
|
|
350
|
+
if (args.title)
|
|
351
|
+
pptx.title = String(args.title);
|
|
352
|
+
if (args.author)
|
|
353
|
+
pptx.author = String(args.author);
|
|
354
|
+
const layout = String(args.layout || 'LAYOUT_16x9');
|
|
355
|
+
pptx.layout = layout;
|
|
356
|
+
const [slideW, slideH] = LAYOUT_DIMS[layout] || LAYOUT_DIMS.LAYOUT_16x9;
|
|
357
|
+
// 应用预设模板
|
|
358
|
+
let templateInfo;
|
|
359
|
+
if (args.template) {
|
|
360
|
+
const tpl = applyTemplate(pptx, String(args.template));
|
|
361
|
+
if (tpl) {
|
|
362
|
+
templateInfo = `已应用模板 "${tpl.name}"(${tpl.description})。可用母版:${tpl.masters.map(m => m.title).join('、')}。颜色参考 — 主色: ${tpl.colors.primary}, 文字色: ${tpl.colors.text}, 背景色: ${tpl.colors.background}`;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const state = { pptx, slides: [], slideW, slideH };
|
|
366
|
+
const docId = sessions.create('pptx', filename, state);
|
|
367
|
+
const result = {
|
|
368
|
+
docId,
|
|
369
|
+
filename,
|
|
370
|
+
slideWidth: slideW,
|
|
371
|
+
slideHeight: slideH,
|
|
372
|
+
message: `PPT 已创建(${slideW}×${slideH} 英寸)。所有元素的 x/y/w/h 均以英寸为单位,请确保 x+w ≤ ${slideW},y+h ≤ ${slideH}`,
|
|
373
|
+
};
|
|
374
|
+
if (templateInfo)
|
|
375
|
+
result.template = templateInfo;
|
|
376
|
+
return JSON.stringify(result);
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
// ---- ppt_add_slide ----
|
|
380
|
+
tools.register({
|
|
381
|
+
definition: {
|
|
382
|
+
type: 'function',
|
|
383
|
+
function: {
|
|
384
|
+
name: 'ppt_add_slide',
|
|
385
|
+
description: [
|
|
386
|
+
'向 PPT 添加新幻灯片。返回幻灯片编号(从 1 开始)。',
|
|
387
|
+
'支持 count 参数一次性批量添加多张幻灯片。',
|
|
388
|
+
'',
|
|
389
|
+
'【子任务协作模式】如果计划将文稿分给多个子任务并行编辑:',
|
|
390
|
+
'1. 先在主会话中用 count 参数一次性预创建所有幻灯片',
|
|
391
|
+
'2. 将具体的幻灯片编号范围分配给各子任务(如"负责幻灯片 1-3")',
|
|
392
|
+
'3. 子任务不应调用 ppt_add_slide,只操作已分配的幻灯片编号',
|
|
393
|
+
'这样可避免并发添加导致的页面顺序混乱。',
|
|
394
|
+
].join('\n'),
|
|
395
|
+
parameters: {
|
|
396
|
+
type: 'object',
|
|
397
|
+
properties: {
|
|
398
|
+
docId: { type: 'string', description: '文档会话 ID' },
|
|
399
|
+
masterName: { type: 'string', description: '母版名称(可选)' },
|
|
400
|
+
background: {
|
|
401
|
+
type: 'object',
|
|
402
|
+
description: '幻灯片背景',
|
|
403
|
+
properties: {
|
|
404
|
+
color: { type: 'string', description: '背景色(十六进制如 "003366")' },
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
count: { type: 'number', description: '批量添加的幻灯片数量(默认 1)。用于子任务协作前预创建所有页面。' },
|
|
408
|
+
},
|
|
409
|
+
required: ['docId'],
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
async handler(args) {
|
|
414
|
+
const state = requireState(String(args.docId));
|
|
415
|
+
const count = Math.max(1, Math.min(Number(args.count) || 1, 50));
|
|
416
|
+
const opts = {};
|
|
417
|
+
if (args.masterName)
|
|
418
|
+
opts.masterName = String(args.masterName);
|
|
419
|
+
const bg = args.background;
|
|
420
|
+
const added = [];
|
|
421
|
+
for (let i = 0; i < count; i++) {
|
|
422
|
+
const slide = state.pptx.addSlide(opts);
|
|
423
|
+
if (bg?.color)
|
|
424
|
+
slide.background = { color: String(bg.color) };
|
|
425
|
+
state.slides.push(slide);
|
|
426
|
+
added.push(state.slides.length);
|
|
427
|
+
}
|
|
428
|
+
if (count === 1) {
|
|
429
|
+
return JSON.stringify({ slideNumber: added[0], message: `幻灯片 ${added[0]} 已添加` });
|
|
430
|
+
}
|
|
431
|
+
return JSON.stringify({
|
|
432
|
+
slideNumbers: added,
|
|
433
|
+
total: state.slides.length,
|
|
434
|
+
message: `已批量添加 ${count} 张幻灯片(编号 ${added[0]}-${added[added.length - 1]}),共 ${state.slides.length} 张`,
|
|
435
|
+
});
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
// ---- ppt_add_text ----
|
|
439
|
+
tools.register({
|
|
440
|
+
definition: {
|
|
441
|
+
type: 'function',
|
|
442
|
+
function: {
|
|
443
|
+
name: 'ppt_add_text',
|
|
444
|
+
description: '在 PPT 幻灯片上添加文本框。坐标/尺寸以英寸为单位,会自动钳制到幻灯片范围内。16:9 页面为 10×5.63 英寸。建议标题 y=0.3~0.5,正文 y=1.5~2,留 0.3~0.5 英寸页边距。',
|
|
445
|
+
parameters: {
|
|
446
|
+
type: 'object',
|
|
447
|
+
properties: {
|
|
448
|
+
docId: { type: 'string', description: '文档会话 ID' },
|
|
449
|
+
slideNumber: { type: 'number', description: '幻灯片编号(从 1 开始),默认最后一张' },
|
|
450
|
+
text: { type: 'string', description: '文本内容(简单文本时使用)' },
|
|
451
|
+
richText: {
|
|
452
|
+
type: 'array',
|
|
453
|
+
description: '富文本段落数组(与 text 二选一)',
|
|
454
|
+
items: {
|
|
455
|
+
type: 'object',
|
|
456
|
+
properties: {
|
|
457
|
+
text: { type: 'string' },
|
|
458
|
+
bold: { type: 'boolean' },
|
|
459
|
+
italic: { type: 'boolean' },
|
|
460
|
+
fontSize: { type: 'number', description: '字号(磅)' },
|
|
461
|
+
fontFace: { type: 'string' },
|
|
462
|
+
color: { type: 'string', description: '十六进制颜色' },
|
|
463
|
+
breakLine: { type: 'boolean', description: '后跟换行' },
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
x: { type: 'number', description: 'X 位置(英寸),默认 0.5' },
|
|
468
|
+
y: { type: 'number', description: 'Y 位置(英寸),默认 0.5' },
|
|
469
|
+
w: { type: 'number', description: '宽度(英寸),默认 9' },
|
|
470
|
+
h: { type: 'number', description: '高度(英寸),默认 1' },
|
|
471
|
+
align: { type: 'string', enum: ['left', 'center', 'right'], description: '对齐方式' },
|
|
472
|
+
valign: { type: 'string', enum: ['top', 'middle', 'bottom'], description: '垂直对齐' },
|
|
473
|
+
fontSize: { type: 'number', description: '默认字号' },
|
|
474
|
+
fontFace: { type: 'string', description: '默认字体' },
|
|
475
|
+
color: { type: 'string', description: '默认颜色' },
|
|
476
|
+
bold: { type: 'boolean' },
|
|
477
|
+
},
|
|
478
|
+
required: ['docId'],
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
async handler(args) {
|
|
483
|
+
const state = requireState(String(args.docId));
|
|
484
|
+
const slide = getSlide(state, args.slideNumber);
|
|
485
|
+
const bounds = clampBounds(state, args.x, args.y, args.w, args.h, {
|
|
486
|
+
x: 0.5,
|
|
487
|
+
y: 0.5,
|
|
488
|
+
w: 9,
|
|
489
|
+
h: 1,
|
|
490
|
+
});
|
|
491
|
+
const opts = {
|
|
492
|
+
...bounds,
|
|
493
|
+
align: args.align || undefined,
|
|
494
|
+
valign: args.valign || undefined,
|
|
495
|
+
fontSize: args.fontSize ? Number(args.fontSize) : undefined,
|
|
496
|
+
fontFace: args.fontFace ? String(args.fontFace) : undefined,
|
|
497
|
+
color: args.color ? String(args.color) : undefined,
|
|
498
|
+
bold: args.bold,
|
|
499
|
+
};
|
|
500
|
+
const richText = args.richText;
|
|
501
|
+
if (richText) {
|
|
502
|
+
const parts = richText.map(rt => ({
|
|
503
|
+
text: String(rt.text || ''),
|
|
504
|
+
options: {
|
|
505
|
+
bold: rt.bold,
|
|
506
|
+
italic: rt.italic,
|
|
507
|
+
fontSize: rt.fontSize ? Number(rt.fontSize) : undefined,
|
|
508
|
+
fontFace: rt.fontFace ? String(rt.fontFace) : undefined,
|
|
509
|
+
color: rt.color ? String(rt.color) : undefined,
|
|
510
|
+
breakLine: rt.breakLine,
|
|
511
|
+
},
|
|
512
|
+
}));
|
|
513
|
+
slide.addText(parts, opts);
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
slide.addText(String(args.text || ''), opts);
|
|
517
|
+
}
|
|
518
|
+
return JSON.stringify({
|
|
519
|
+
success: true,
|
|
520
|
+
message: `文本已添加到幻灯片 ${args.slideNumber || state.slides.length}`,
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
// ---- ppt_add_image ----
|
|
525
|
+
tools.register({
|
|
526
|
+
definition: {
|
|
527
|
+
type: 'function',
|
|
528
|
+
function: {
|
|
529
|
+
name: 'ppt_add_image',
|
|
530
|
+
description: '在 PPT 幻灯片上添加图片。坐标/尺寸以英寸为单位,会自动钳制到幻灯片范围内。建议图片宽度不超过 9 英寸(16:9 留边距),高度不超过 4 英寸。',
|
|
531
|
+
parameters: {
|
|
532
|
+
type: 'object',
|
|
533
|
+
properties: {
|
|
534
|
+
docId: { type: 'string', description: '文档会话 ID' },
|
|
535
|
+
slideNumber: { type: 'number', description: '幻灯片编号' },
|
|
536
|
+
source: { type: 'string', description: '图片来源:URL 或本地路径' },
|
|
537
|
+
x: { type: 'number', description: 'X 位置(英寸),默认 0.5' },
|
|
538
|
+
y: { type: 'number', description: 'Y 位置(英寸),默认 0.5' },
|
|
539
|
+
w: { type: 'number', description: '宽度(英寸),默认 5' },
|
|
540
|
+
h: { type: 'number', description: '高度(英寸),默认 3.75' },
|
|
541
|
+
},
|
|
542
|
+
required: ['docId', 'source'],
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
async handler(args) {
|
|
547
|
+
const state = requireState(String(args.docId));
|
|
548
|
+
const slide = getSlide(state, args.slideNumber);
|
|
549
|
+
const { buffer, mime } = await loadImage(storage, String(args.source), outputUri);
|
|
550
|
+
const base64 = buffer.toString('base64');
|
|
551
|
+
const ext = mime.includes('png') ? 'png' : mime.includes('gif') ? 'gif' : 'jpeg';
|
|
552
|
+
const bounds = clampBounds(state, args.x, args.y, args.w, args.h, {
|
|
553
|
+
x: 0.5,
|
|
554
|
+
y: 0.5,
|
|
555
|
+
w: 5,
|
|
556
|
+
h: 3.75,
|
|
557
|
+
});
|
|
558
|
+
slide.addImage({
|
|
559
|
+
data: `image/${ext};base64,${base64}`,
|
|
560
|
+
...bounds,
|
|
561
|
+
});
|
|
562
|
+
return JSON.stringify({ success: true, message: `图片已添加`, position: bounds });
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
// ---- ppt_add_shape ----
|
|
566
|
+
tools.register({
|
|
567
|
+
definition: {
|
|
568
|
+
type: 'function',
|
|
569
|
+
function: {
|
|
570
|
+
name: 'ppt_add_shape',
|
|
571
|
+
description: '在 PPT 幻灯片上添加形状(矩形、圆形、箭头等)。坐标/尺寸以英寸为单位,会自动钳制到幻灯片范围内。',
|
|
572
|
+
parameters: {
|
|
573
|
+
type: 'object',
|
|
574
|
+
properties: {
|
|
575
|
+
docId: { type: 'string', description: '文档会话 ID' },
|
|
576
|
+
slideNumber: { type: 'number', description: '幻灯片编号' },
|
|
577
|
+
shape: {
|
|
578
|
+
type: 'string',
|
|
579
|
+
enum: ['rect', 'ellipse', 'roundRect', 'triangle', 'diamond', 'line', 'arrow'],
|
|
580
|
+
description: '形状类型',
|
|
581
|
+
},
|
|
582
|
+
x: { type: 'number', description: 'X 位置(英寸),默认 1' },
|
|
583
|
+
y: { type: 'number', description: 'Y 位置(英寸),默认 1' },
|
|
584
|
+
w: { type: 'number', description: '宽度(英寸),默认 2' },
|
|
585
|
+
h: { type: 'number', description: '高度(英寸),默认 2' },
|
|
586
|
+
fill: { type: 'string', description: '填充色(十六进制)' },
|
|
587
|
+
line: { type: 'object', properties: { color: { type: 'string' }, width: { type: 'number' } } },
|
|
588
|
+
text: { type: 'string', description: '形状内的文字' },
|
|
589
|
+
},
|
|
590
|
+
required: ['docId', 'shape'],
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
async handler(args) {
|
|
595
|
+
const state = requireState(String(args.docId));
|
|
596
|
+
const slide = getSlide(state, args.slideNumber);
|
|
597
|
+
const shapeMap = {
|
|
598
|
+
rect: state.pptx.ShapeType.rect,
|
|
599
|
+
ellipse: state.pptx.ShapeType.ellipse,
|
|
600
|
+
roundRect: state.pptx.ShapeType.roundRect,
|
|
601
|
+
triangle: state.pptx.ShapeType.triangle,
|
|
602
|
+
diamond: state.pptx.ShapeType.diamond,
|
|
603
|
+
line: state.pptx.ShapeType.line,
|
|
604
|
+
arrow: state.pptx.ShapeType.rightArrow,
|
|
605
|
+
};
|
|
606
|
+
const shapeType = shapeMap[String(args.shape)] || state.pptx.ShapeType.rect;
|
|
607
|
+
const lineArg = args.line;
|
|
608
|
+
const bounds = clampBounds(state, args.x, args.y, args.w, args.h, {
|
|
609
|
+
x: 1,
|
|
610
|
+
y: 1,
|
|
611
|
+
w: 2,
|
|
612
|
+
h: 2,
|
|
613
|
+
});
|
|
614
|
+
const opts = {
|
|
615
|
+
...bounds,
|
|
616
|
+
fill: args.fill ? { color: String(args.fill) } : undefined,
|
|
617
|
+
line: lineArg
|
|
618
|
+
? {
|
|
619
|
+
color: lineArg.color ? String(lineArg.color) : undefined,
|
|
620
|
+
width: lineArg.width ? Number(lineArg.width) : undefined,
|
|
621
|
+
}
|
|
622
|
+
: undefined,
|
|
623
|
+
};
|
|
624
|
+
slide.addShape(shapeType, opts);
|
|
625
|
+
if (args.text) {
|
|
626
|
+
slide.addText(String(args.text), {
|
|
627
|
+
...bounds,
|
|
628
|
+
align: 'center',
|
|
629
|
+
valign: 'middle',
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
return JSON.stringify({ success: true, message: `形状 ${args.shape} 已添加`, position: bounds });
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
// ---- ppt_add_chart ----
|
|
636
|
+
tools.register({
|
|
637
|
+
definition: {
|
|
638
|
+
type: 'function',
|
|
639
|
+
function: {
|
|
640
|
+
name: 'ppt_add_chart',
|
|
641
|
+
description: '在 PPT 幻灯片上添加图表。坐标/尺寸以英寸为单位,会自动钳制到幻灯片范围内。',
|
|
642
|
+
parameters: {
|
|
643
|
+
type: 'object',
|
|
644
|
+
properties: {
|
|
645
|
+
docId: { type: 'string', description: '文档会话 ID' },
|
|
646
|
+
slideNumber: { type: 'number', description: '幻灯片编号' },
|
|
647
|
+
chartType: {
|
|
648
|
+
type: 'string',
|
|
649
|
+
enum: ['bar', 'bar3d', 'line', 'pie', 'doughnut', 'area', 'scatter'],
|
|
650
|
+
description: '图表类型',
|
|
651
|
+
},
|
|
652
|
+
title: { type: 'string', description: '图表标题' },
|
|
653
|
+
data: {
|
|
654
|
+
type: 'array',
|
|
655
|
+
description: '数据系列',
|
|
656
|
+
items: {
|
|
657
|
+
type: 'object',
|
|
658
|
+
properties: {
|
|
659
|
+
name: { type: 'string', description: '系列名' },
|
|
660
|
+
labels: { type: 'array', items: { type: 'string' }, description: '分类标签' },
|
|
661
|
+
values: { type: 'array', items: { type: 'number' }, description: '数据值' },
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
},
|
|
665
|
+
x: { type: 'number', description: 'X 位置(英寸),默认 0.5' },
|
|
666
|
+
y: { type: 'number', description: 'Y 位置(英寸),默认 1' },
|
|
667
|
+
w: { type: 'number', description: '宽度(英寸),默认 9' },
|
|
668
|
+
h: { type: 'number', description: '高度(英寸),默认 5' },
|
|
669
|
+
showLegend: { type: 'boolean', description: '显示图例' },
|
|
670
|
+
showValue: { type: 'boolean', description: '显示数值标签' },
|
|
671
|
+
},
|
|
672
|
+
required: ['docId', 'chartType', 'data'],
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
},
|
|
676
|
+
async handler(args) {
|
|
677
|
+
const state = requireState(String(args.docId));
|
|
678
|
+
const slide = getSlide(state, args.slideNumber);
|
|
679
|
+
const chartTypeMap = {
|
|
680
|
+
bar: state.pptx.ChartType.bar,
|
|
681
|
+
bar3d: state.pptx.ChartType.bar3d,
|
|
682
|
+
line: state.pptx.ChartType.line,
|
|
683
|
+
pie: state.pptx.ChartType.pie,
|
|
684
|
+
doughnut: state.pptx.ChartType.doughnut,
|
|
685
|
+
area: state.pptx.ChartType.area,
|
|
686
|
+
scatter: state.pptx.ChartType.scatter,
|
|
687
|
+
};
|
|
688
|
+
const type = chartTypeMap[String(args.chartType)] || state.pptx.ChartType.bar;
|
|
689
|
+
const data = args.data;
|
|
690
|
+
const bounds = clampBounds(state, args.x, args.y, args.w, args.h, {
|
|
691
|
+
x: 0.5,
|
|
692
|
+
y: 1,
|
|
693
|
+
w: 9,
|
|
694
|
+
h: 5,
|
|
695
|
+
});
|
|
696
|
+
slide.addChart(type, data, {
|
|
697
|
+
...bounds,
|
|
698
|
+
showTitle: !!args.title,
|
|
699
|
+
title: args.title ? String(args.title) : undefined,
|
|
700
|
+
showLegend: args.showLegend !== false,
|
|
701
|
+
showValue: args.showValue === true,
|
|
702
|
+
});
|
|
703
|
+
return JSON.stringify({ success: true, message: `图表已添加`, position: bounds });
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
// ---- ppt_add_table ----
|
|
707
|
+
tools.register({
|
|
708
|
+
definition: {
|
|
709
|
+
type: 'function',
|
|
710
|
+
function: {
|
|
711
|
+
name: 'ppt_add_table',
|
|
712
|
+
description: '在 PPT 幻灯片上添加表格。坐标/尺寸以英寸为单位,会自动钳制到幻灯片范围内。',
|
|
713
|
+
parameters: {
|
|
714
|
+
type: 'object',
|
|
715
|
+
properties: {
|
|
716
|
+
docId: { type: 'string', description: '文档会话 ID' },
|
|
717
|
+
slideNumber: { type: 'number', description: '幻灯片编号' },
|
|
718
|
+
headers: { type: 'array', items: { type: 'string' }, description: '表头' },
|
|
719
|
+
rows: { type: 'array', items: { type: 'array', items: {} }, description: '数据行' },
|
|
720
|
+
x: { type: 'number', description: 'X 位置(英寸),默认 0.5' },
|
|
721
|
+
y: { type: 'number', description: 'Y 位置(英寸),默认 1' },
|
|
722
|
+
w: { type: 'number', description: '宽度(英寸),默认 9' },
|
|
723
|
+
headerColor: { type: 'string', description: '表头背景色' },
|
|
724
|
+
headerFontColor: { type: 'string', description: '表头文字颜色,默认白色' },
|
|
725
|
+
fontSize: { type: 'number', description: '字号' },
|
|
726
|
+
},
|
|
727
|
+
required: ['docId', 'rows'],
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
async handler(args) {
|
|
732
|
+
const state = requireState(String(args.docId));
|
|
733
|
+
const slide = getSlide(state, args.slideNumber);
|
|
734
|
+
const headers = args.headers;
|
|
735
|
+
const rows = args.rows;
|
|
736
|
+
const tableRows = [];
|
|
737
|
+
if (headers) {
|
|
738
|
+
tableRows.push(headers.map(h => ({
|
|
739
|
+
text: String(h),
|
|
740
|
+
options: {
|
|
741
|
+
bold: true,
|
|
742
|
+
fill: { color: args.headerColor ? String(args.headerColor) : '4472C4' },
|
|
743
|
+
color: args.headerFontColor ? String(args.headerFontColor) : 'FFFFFF',
|
|
744
|
+
},
|
|
745
|
+
})));
|
|
746
|
+
}
|
|
747
|
+
for (const row of rows) {
|
|
748
|
+
tableRows.push(row.map(cell => ({ text: cell == null ? '' : String(cell) })));
|
|
749
|
+
}
|
|
750
|
+
const bounds = clampBounds(state, args.x, args.y, args.w, undefined, {
|
|
751
|
+
x: 0.5,
|
|
752
|
+
y: 1,
|
|
753
|
+
w: 9,
|
|
754
|
+
h: 5,
|
|
755
|
+
});
|
|
756
|
+
slide.addTable(tableRows, {
|
|
757
|
+
x: bounds.x,
|
|
758
|
+
y: bounds.y,
|
|
759
|
+
w: bounds.w,
|
|
760
|
+
fontSize: args.fontSize ? Number(args.fontSize) : 12,
|
|
761
|
+
border: { type: 'solid', pt: 0.5, color: 'CCCCCC' },
|
|
762
|
+
});
|
|
763
|
+
return JSON.stringify({
|
|
764
|
+
success: true,
|
|
765
|
+
message: `表格已添加`,
|
|
766
|
+
position: { x: bounds.x, y: bounds.y, w: bounds.w },
|
|
767
|
+
});
|
|
768
|
+
},
|
|
769
|
+
});
|
|
770
|
+
// ---- ppt_set_transition ----
|
|
771
|
+
tools.register({
|
|
772
|
+
definition: {
|
|
773
|
+
type: 'function',
|
|
774
|
+
function: {
|
|
775
|
+
name: 'ppt_set_transition',
|
|
776
|
+
description: '设置 PPT 幻灯片的切换效果。',
|
|
777
|
+
parameters: {
|
|
778
|
+
type: 'object',
|
|
779
|
+
properties: {
|
|
780
|
+
docId: { type: 'string', description: '文档会话 ID' },
|
|
781
|
+
slideNumber: { type: 'number', description: '幻灯片编号' },
|
|
782
|
+
type: { type: 'string', enum: ['fade', 'push', 'wipe', 'zoom', 'none'], description: '切换类型' },
|
|
783
|
+
speed: { type: 'number', description: '切换速度(秒),默认 1' },
|
|
784
|
+
},
|
|
785
|
+
required: ['docId', 'type'],
|
|
786
|
+
},
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
async handler(args) {
|
|
790
|
+
// pptxgenjs 类型定义不暴露 transition 属性,但运行时支持
|
|
791
|
+
return JSON.stringify({
|
|
792
|
+
success: false,
|
|
793
|
+
message: `pptxgenjs 的类型定义不支持设置幻灯片切换效果。建议在 PowerPoint 中手动设置 "${args.type}" 切换。`,
|
|
794
|
+
});
|
|
795
|
+
},
|
|
796
|
+
});
|
|
797
|
+
// ---- ppt_set_animation ----
|
|
798
|
+
tools.register({
|
|
799
|
+
definition: {
|
|
800
|
+
type: 'function',
|
|
801
|
+
function: {
|
|
802
|
+
name: 'ppt_set_animation',
|
|
803
|
+
description: '提示:pptxgenjs 不完全支持单个元素动画。如需动画效果,建议在 PPT 编辑器中手动设置。此工具仅作占位。',
|
|
804
|
+
parameters: {
|
|
805
|
+
type: 'object',
|
|
806
|
+
properties: {
|
|
807
|
+
docId: { type: 'string', description: '文档会话 ID' },
|
|
808
|
+
slideNumber: { type: 'number' },
|
|
809
|
+
effect: { type: 'string', enum: ['appear', 'fadeIn', 'flyIn', 'zoomIn'] },
|
|
810
|
+
},
|
|
811
|
+
required: ['docId', 'effect'],
|
|
812
|
+
},
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
async handler(args) {
|
|
816
|
+
return JSON.stringify({
|
|
817
|
+
success: false,
|
|
818
|
+
message: `pptxgenjs 暂不支持单元素动画。建议在 PowerPoint 编辑器中手动为元素添加 "${args.effect}" 动画效果。幻灯片切换动画请使用 ppt_set_transition 工具。`,
|
|
819
|
+
});
|
|
820
|
+
},
|
|
821
|
+
});
|
|
822
|
+
// ---- ppt_set_master ----
|
|
823
|
+
tools.register({
|
|
824
|
+
definition: {
|
|
825
|
+
type: 'function',
|
|
826
|
+
function: {
|
|
827
|
+
name: 'ppt_set_master',
|
|
828
|
+
description: '定义一个幻灯片母版/模板,之后可在添加幻灯片时引用。支持设置背景、固定元素等。元素坐标会自动钳制到幻灯片范围内。',
|
|
829
|
+
parameters: {
|
|
830
|
+
type: 'object',
|
|
831
|
+
properties: {
|
|
832
|
+
docId: { type: 'string', description: '文档会话 ID' },
|
|
833
|
+
name: { type: 'string', description: '母版名称' },
|
|
834
|
+
background: { type: 'object', properties: { color: { type: 'string' } } },
|
|
835
|
+
objects: {
|
|
836
|
+
type: 'array',
|
|
837
|
+
description: '母版上的固定元素',
|
|
838
|
+
items: {
|
|
839
|
+
type: 'object',
|
|
840
|
+
properties: {
|
|
841
|
+
type: { type: 'string', enum: ['text', 'rect'] },
|
|
842
|
+
text: { type: 'string' },
|
|
843
|
+
x: { type: 'number' },
|
|
844
|
+
y: { type: 'number' },
|
|
845
|
+
w: { type: 'number' },
|
|
846
|
+
h: { type: 'number' },
|
|
847
|
+
color: { type: 'string' },
|
|
848
|
+
fontSize: { type: 'number' },
|
|
849
|
+
bold: { type: 'boolean' },
|
|
850
|
+
fill: { type: 'string' },
|
|
851
|
+
},
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
required: ['docId', 'name'],
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
async handler(args) {
|
|
860
|
+
const state = requireState(String(args.docId));
|
|
861
|
+
const bg = args.background;
|
|
862
|
+
const objects = args.objects;
|
|
863
|
+
const masterObjects = [];
|
|
864
|
+
if (objects) {
|
|
865
|
+
for (const obj of objects) {
|
|
866
|
+
const b = clampBounds(state, obj.x, obj.y, obj.w, obj.h, {
|
|
867
|
+
x: 0,
|
|
868
|
+
y: 0,
|
|
869
|
+
w: state.slideW,
|
|
870
|
+
h: 0.5,
|
|
871
|
+
});
|
|
872
|
+
if (obj.type === 'text') {
|
|
873
|
+
masterObjects.push({
|
|
874
|
+
text: {
|
|
875
|
+
text: String(obj.text || ''),
|
|
876
|
+
options: {
|
|
877
|
+
...b,
|
|
878
|
+
color: obj.color ? String(obj.color) : undefined,
|
|
879
|
+
fontSize: obj.fontSize ? Number(obj.fontSize) : undefined,
|
|
880
|
+
bold: obj.bold,
|
|
881
|
+
},
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
else if (obj.type === 'rect') {
|
|
886
|
+
masterObjects.push({
|
|
887
|
+
rect: {
|
|
888
|
+
...b,
|
|
889
|
+
fill: obj.fill ? { color: String(obj.fill) } : undefined,
|
|
890
|
+
},
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
state.pptx.defineSlideMaster({
|
|
896
|
+
title: String(args.name),
|
|
897
|
+
background: bg?.color ? { color: String(bg.color) } : undefined,
|
|
898
|
+
objects: masterObjects.length > 0 ? masterObjects : undefined,
|
|
899
|
+
});
|
|
900
|
+
return JSON.stringify({
|
|
901
|
+
success: true,
|
|
902
|
+
message: `母版 "${args.name}" 已定义。添加幻灯片时可通过 masterName 参数引用。`,
|
|
903
|
+
});
|
|
904
|
+
},
|
|
905
|
+
});
|
|
906
|
+
// ---- ppt_save ----
|
|
907
|
+
tools.register({
|
|
908
|
+
definition: {
|
|
909
|
+
type: 'function',
|
|
910
|
+
function: {
|
|
911
|
+
name: 'ppt_save',
|
|
912
|
+
description: '保存 PPT 演示文稿到文件并释放文档会话。如果使用了子任务协作编辑,请确保所有子任务完成后再调用此工具保存。',
|
|
913
|
+
parameters: {
|
|
914
|
+
type: 'object',
|
|
915
|
+
properties: {
|
|
916
|
+
docId: { type: 'string', description: '文档会话 ID' },
|
|
917
|
+
},
|
|
918
|
+
required: ['docId'],
|
|
919
|
+
},
|
|
920
|
+
},
|
|
921
|
+
},
|
|
922
|
+
async handler(args) {
|
|
923
|
+
const session = sessions.require(String(args.docId), 'pptx');
|
|
924
|
+
const state = session.doc;
|
|
925
|
+
const fileUri = joinUri(outputUri, session.filename);
|
|
926
|
+
const buffer = (await state.pptx.write({ outputType: 'nodebuffer' }));
|
|
927
|
+
await storage.writeFile(fileUri, buffer);
|
|
928
|
+
const slideCount = state.slides.length;
|
|
929
|
+
sessions.remove(session.id);
|
|
930
|
+
return JSON.stringify({
|
|
931
|
+
success: true,
|
|
932
|
+
path: fileUri,
|
|
933
|
+
slides: slideCount,
|
|
934
|
+
size: buffer.length,
|
|
935
|
+
message: `PPT 已保存: ${fileUri}`,
|
|
936
|
+
});
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
//# sourceMappingURL=pptx.js.map
|