pptx-viewer-mcp 1.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/dist/index.js ADDED
@@ -0,0 +1,1581 @@
1
+ 'use strict';
2
+
3
+ var pptxViewerCore = require('pptx-viewer-core');
4
+
5
+ // src/execution.ts
6
+ var PPTX_MIME_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
7
+ async function loadPresentation(filePath, execCtx) {
8
+ const rawBytes = await execCtx.filesystem.readFile(filePath);
9
+ if (execCtx.collaboration) {
10
+ const room = execCtx.collaboration.getRoom(filePath);
11
+ if (room) {
12
+ const codec = execCtx.collaboration.getCodec(filePath);
13
+ if (codec) {
14
+ const currentBytes = await codec.dehydrate(room.ydoc);
15
+ const handler2 = new pptxViewerCore.PptxHandler();
16
+ const pptxData2 = await handler2.load(currentBytes.buffer);
17
+ return { pptxData: pptxData2, rawBytes };
18
+ }
19
+ }
20
+ }
21
+ const handler = new pptxViewerCore.PptxHandler();
22
+ const pptxData = await handler.load(rawBytes.buffer);
23
+ return { pptxData, rawBytes };
24
+ }
25
+ async function savePresentation(filePath, pptxData, rawBytes, execCtx) {
26
+ const handler = new pptxViewerCore.PptxHandler();
27
+ await handler.load(rawBytes.buffer);
28
+ const outputBytes = await handler.save(pptxData.slides, {
29
+ headerFooter: pptxData.headerFooter
30
+ });
31
+ if (execCtx.collaboration) {
32
+ const room = execCtx.collaboration.getRoom(filePath);
33
+ if (room) {
34
+ const codec = execCtx.collaboration.getCodec(filePath);
35
+ if (codec) {
36
+ const origin = execCtx.collaboration.agentOrigin(execCtx.agentName ?? "pptx-tool");
37
+ await codec.hydrate(room.ydoc, outputBytes, origin);
38
+ if (execCtx.viewer) {
39
+ execCtx.viewer.replaceContent(filePath, outputBytes, {
40
+ markDirty: false,
41
+ mimeType: PPTX_MIME_TYPE
42
+ });
43
+ }
44
+ return { savedToDisk: false, routedThroughCollaboration: true };
45
+ }
46
+ }
47
+ }
48
+ await execCtx.filesystem.writeFile(filePath, outputBytes);
49
+ if (execCtx.viewer) {
50
+ execCtx.viewer.replaceContent(filePath, outputBytes, {
51
+ markDirty: false,
52
+ mimeType: PPTX_MIME_TYPE
53
+ });
54
+ }
55
+ return { savedToDisk: true, routedThroughCollaboration: false };
56
+ }
57
+ async function executeToolWithContext(filePath, execCtx, toolFn) {
58
+ const { pptxData, rawBytes } = await loadPresentation(filePath, execCtx);
59
+ const toolResult = await toolFn({ pptxData });
60
+ if (toolResult.dirty) {
61
+ const saveResult = await savePresentation(filePath, toolResult.pptxData, rawBytes, execCtx);
62
+ return { ...toolResult.result, ...saveResult };
63
+ }
64
+ return toolResult.result;
65
+ }
66
+ function generateElementId() {
67
+ return `el-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
68
+ }
69
+ function generateSlideId() {
70
+ return `slide-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
71
+ }
72
+ function describeElement(el) {
73
+ const base = {
74
+ id: el.id,
75
+ type: el.type,
76
+ x: Math.round(el.x),
77
+ y: Math.round(el.y),
78
+ width: Math.round(el.width),
79
+ height: Math.round(el.height)
80
+ };
81
+ if (el.rotation) {
82
+ base.rotation = el.rotation;
83
+ }
84
+ if (el.hidden) {
85
+ base.hidden = true;
86
+ }
87
+ if (el.opacity !== void 0 && el.opacity !== 1) {
88
+ base.opacity = el.opacity;
89
+ }
90
+ if (pptxViewerCore.hasTextProperties(el)) {
91
+ base.text = el.text ?? "";
92
+ if (el.textStyle) {
93
+ const ts = {};
94
+ if (el.textStyle.fontSize) {
95
+ ts.fontSize = el.textStyle.fontSize;
96
+ }
97
+ if (el.textStyle.fontFamily) {
98
+ ts.fontFamily = el.textStyle.fontFamily;
99
+ }
100
+ if (el.textStyle.bold) {
101
+ ts.bold = true;
102
+ }
103
+ if (el.textStyle.italic) {
104
+ ts.italic = true;
105
+ }
106
+ if (el.textStyle.underline) {
107
+ ts.underline = el.textStyle.underline;
108
+ }
109
+ if (el.textStyle.align) {
110
+ ts.align = el.textStyle.align;
111
+ }
112
+ if (el.textStyle.color) {
113
+ ts.color = el.textStyle.color;
114
+ }
115
+ if (Object.keys(ts).length > 0) {
116
+ base.textStyle = ts;
117
+ }
118
+ }
119
+ }
120
+ if ("shapeType" in el && el.shapeType) {
121
+ base.shapeType = el.shapeType;
122
+ }
123
+ if ("shapeStyle" in el && el.shapeStyle) {
124
+ const ss = {};
125
+ const style = el.shapeStyle;
126
+ if (style.fillColor) {
127
+ ss.fillColor = style.fillColor;
128
+ }
129
+ if (style.strokeColor) {
130
+ ss.strokeColor = style.strokeColor;
131
+ }
132
+ if (style.strokeWidth) {
133
+ ss.strokeWidth = style.strokeWidth;
134
+ }
135
+ if (Object.keys(ss).length > 0) {
136
+ base.shapeStyle = ss;
137
+ }
138
+ }
139
+ if (el.type === "image" || el.type === "picture") {
140
+ const img = el;
141
+ if (img.altText) {
142
+ base.altText = img.altText;
143
+ }
144
+ if (img.imagePath) {
145
+ base.imagePath = img.imagePath;
146
+ }
147
+ base.hasImageData = Boolean(img.imageData);
148
+ }
149
+ if (el.type === "table") {
150
+ const tbl = el;
151
+ if (tbl.tableData) {
152
+ base.rowCount = tbl.tableData.rows.length;
153
+ base.columnCount = tbl.tableData.columnWidths.length;
154
+ base.cells = tbl.tableData.rows.map((row) => row.cells.map((cell) => cell.text ?? ""));
155
+ }
156
+ }
157
+ if (el.type === "chart") {
158
+ base.chartType = el.chartData?.type;
159
+ }
160
+ if (el.type === "group") {
161
+ const grp = el;
162
+ base.childCount = grp.children?.length ?? 0;
163
+ }
164
+ return base;
165
+ }
166
+ function extractSlideText(slide) {
167
+ const texts = [];
168
+ for (const el of slide.elements) {
169
+ if (pptxViewerCore.hasTextProperties(el) && el.text) {
170
+ texts.push(el.text);
171
+ }
172
+ if (el.type === "table") {
173
+ const tbl = el;
174
+ if (tbl.tableData) {
175
+ for (const row of tbl.tableData.rows) {
176
+ for (const cell of row.cells) {
177
+ if (cell.text) {
178
+ texts.push(cell.text);
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ if (slide.notes) {
186
+ texts.push(slide.notes);
187
+ }
188
+ return texts.join(" ");
189
+ }
190
+ function validateSlideIndex(slideIndex, slideCount) {
191
+ if (slideIndex < 0 || slideIndex >= slideCount) {
192
+ return `Slide index ${slideIndex} out of range (0\u2013${slideCount - 1}).`;
193
+ }
194
+ return null;
195
+ }
196
+
197
+ // src/tools/slide-tools.ts
198
+ function getSlide(ctx, params) {
199
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
200
+ if (err) {
201
+ throw new Error(err);
202
+ }
203
+ const slide = ctx.pptxData.slides[params.slideIndex];
204
+ return {
205
+ pptxData: ctx.pptxData,
206
+ dirty: false,
207
+ result: {
208
+ slideIndex: params.slideIndex,
209
+ slideNumber: slide.slideNumber,
210
+ hidden: slide.hidden ?? false,
211
+ layoutName: slide.layoutName,
212
+ backgroundColor: slide.backgroundColor,
213
+ notes: slide.notes ?? "",
214
+ commentCount: slide.comments?.length ?? 0,
215
+ elements: slide.elements.map(describeElement)
216
+ }
217
+ };
218
+ }
219
+ function addSlide(ctx, params) {
220
+ const { slides } = ctx.pptxData;
221
+ const insertAt = params.insertAfterIndex !== void 0 && params.insertAfterIndex >= 0 ? Math.min(params.insertAfterIndex + 1, slides.length) : slides.length;
222
+ const newSlide = {
223
+ id: generateSlideId(),
224
+ rId: "",
225
+ slideNumber: insertAt + 1,
226
+ elements: [],
227
+ backgroundColor: params.backgroundColor
228
+ };
229
+ slides.splice(insertAt, 0, newSlide);
230
+ slides.forEach((s, i) => {
231
+ s.slideNumber = i + 1;
232
+ });
233
+ return {
234
+ pptxData: ctx.pptxData,
235
+ dirty: true,
236
+ result: { newSlideIndex: insertAt, slideCount: slides.length }
237
+ };
238
+ }
239
+ function deleteSlides(ctx, params) {
240
+ const { slides } = ctx.pptxData;
241
+ if (!params.slideIndexes || params.slideIndexes.length === 0) {
242
+ throw new Error("slideIndexes is required for the delete action.");
243
+ }
244
+ const indexSet = new Set(params.slideIndexes);
245
+ const invalid = params.slideIndexes.filter((i) => i < 0 || i >= slides.length);
246
+ if (invalid.length > 0) {
247
+ throw new Error(
248
+ `Invalid slide indexes: ${invalid.join(", ")}. Valid range: 0\u2013${slides.length - 1}.`
249
+ );
250
+ }
251
+ if (indexSet.size >= slides.length) {
252
+ throw new Error("Cannot delete all slides. At least one slide must remain.");
253
+ }
254
+ ctx.pptxData.slides = slides.filter((_, i) => !indexSet.has(i));
255
+ ctx.pptxData.slides.forEach((s, i) => {
256
+ s.slideNumber = i + 1;
257
+ });
258
+ return {
259
+ pptxData: ctx.pptxData,
260
+ dirty: true,
261
+ result: { deletedCount: indexSet.size, slideCount: ctx.pptxData.slides.length }
262
+ };
263
+ }
264
+ function reorderSlides(ctx, params) {
265
+ const { slides } = ctx.pptxData;
266
+ if (!params.newOrder || params.newOrder.length !== slides.length) {
267
+ throw new Error(
268
+ `newOrder must have exactly ${slides.length} indexes. Got ${params.newOrder?.length ?? 0}.`
269
+ );
270
+ }
271
+ const sorted = [...params.newOrder].sort((a, b) => a - b);
272
+ const expected = slides.map((_, i) => i);
273
+ if (JSON.stringify(sorted) !== JSON.stringify(expected)) {
274
+ throw new Error(
275
+ `newOrder must contain each index from 0 to ${slides.length - 1} exactly once.`
276
+ );
277
+ }
278
+ ctx.pptxData.slides = params.newOrder.map((idx) => slides[idx]);
279
+ ctx.pptxData.slides.forEach((s, i) => {
280
+ s.slideNumber = i + 1;
281
+ });
282
+ return {
283
+ pptxData: ctx.pptxData,
284
+ dirty: true,
285
+ result: { slideCount: ctx.pptxData.slides.length }
286
+ };
287
+ }
288
+ function duplicateSlide(ctx, params) {
289
+ const { slides } = ctx.pptxData;
290
+ const err = validateSlideIndex(params.slideIndex, slides.length);
291
+ if (err) {
292
+ throw new Error(err);
293
+ }
294
+ const original = slides[params.slideIndex];
295
+ const clone = structuredClone(original);
296
+ clone.id = generateSlideId();
297
+ clone.rId = "";
298
+ for (const el of clone.elements) {
299
+ el.id = generateElementId();
300
+ }
301
+ const insertIndex = params.targetIndex ?? params.slideIndex + 1;
302
+ slides.splice(insertIndex, 0, clone);
303
+ slides.forEach((s, i) => {
304
+ s.slideNumber = i + 1;
305
+ });
306
+ return {
307
+ pptxData: ctx.pptxData,
308
+ dirty: true,
309
+ result: { newSlideIndex: insertIndex, slideCount: slides.length }
310
+ };
311
+ }
312
+ function updateSlideProperties(ctx, params) {
313
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
314
+ if (err) {
315
+ throw new Error(err);
316
+ }
317
+ const slide = ctx.pptxData.slides[params.slideIndex];
318
+ if (params.backgroundColor !== void 0) {
319
+ slide.backgroundColor = params.backgroundColor;
320
+ }
321
+ if (params.backgroundGradient !== void 0) {
322
+ slide.backgroundGradient = params.backgroundGradient;
323
+ }
324
+ if (params.backgroundImage !== void 0) {
325
+ slide.backgroundImage = params.backgroundImage;
326
+ }
327
+ if (params.notes !== void 0) {
328
+ slide.notes = params.notes;
329
+ }
330
+ if (params.hidden !== void 0) {
331
+ slide.hidden = params.hidden;
332
+ }
333
+ return {
334
+ pptxData: ctx.pptxData,
335
+ dirty: true,
336
+ result: { slideIndex: params.slideIndex }
337
+ };
338
+ }
339
+ function setSlideTransition(ctx, params) {
340
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
341
+ if (err) {
342
+ throw new Error(err);
343
+ }
344
+ const slide = ctx.pptxData.slides[params.slideIndex];
345
+ if (params.type === "none") {
346
+ slide.transition = void 0;
347
+ } else {
348
+ const transition = {
349
+ type: params.type
350
+ };
351
+ if (params.durationMs !== void 0) {
352
+ transition.durationMs = params.durationMs;
353
+ }
354
+ if (params.direction !== void 0) {
355
+ transition.direction = params.direction;
356
+ }
357
+ if (params.advanceOnClick !== void 0) {
358
+ transition.advanceOnClick = params.advanceOnClick;
359
+ }
360
+ if (params.advanceAfterMs !== void 0) {
361
+ transition.advanceAfterMs = params.advanceAfterMs;
362
+ }
363
+ slide.transition = transition;
364
+ }
365
+ return {
366
+ pptxData: ctx.pptxData,
367
+ dirty: true,
368
+ result: { slideIndex: params.slideIndex, transitionType: params.type }
369
+ };
370
+ }
371
+ function setCanvasSize(ctx, params) {
372
+ ctx.pptxData.width = params.width;
373
+ ctx.pptxData.height = params.height;
374
+ if (ctx.pptxData.widthEmu !== void 0) {
375
+ ctx.pptxData.widthEmu = Math.round(params.width * 12700);
376
+ }
377
+ if (ctx.pptxData.heightEmu !== void 0) {
378
+ ctx.pptxData.heightEmu = Math.round(params.height * 12700);
379
+ }
380
+ return {
381
+ pptxData: ctx.pptxData,
382
+ dirty: true,
383
+ result: { canvasWidth: params.width, canvasHeight: params.height }
384
+ };
385
+ }
386
+ function addElement(ctx, params) {
387
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
388
+ if (err) {
389
+ throw new Error(err);
390
+ }
391
+ const slide = ctx.pptxData.slides[params.slideIndex];
392
+ const id = generateElementId();
393
+ const x = params.x ?? 100;
394
+ const y = params.y ?? 100;
395
+ const width = params.width ?? 300;
396
+ const height = params.height ?? 60;
397
+ switch (params.type) {
398
+ case "text": {
399
+ const el = {
400
+ id,
401
+ type: "text",
402
+ x,
403
+ y,
404
+ width,
405
+ height,
406
+ text: params.text ?? "",
407
+ textStyle: {
408
+ fontSize: params.fontSize,
409
+ fontFamily: params.fontFamily,
410
+ color: params.fontColor,
411
+ bold: params.bold,
412
+ italic: params.italic,
413
+ underline: params.underline,
414
+ align: params.alignment
415
+ },
416
+ textSegments: [
417
+ {
418
+ text: params.text ?? "",
419
+ style: {
420
+ fontSize: params.fontSize,
421
+ fontFamily: params.fontFamily,
422
+ color: params.fontColor,
423
+ bold: params.bold,
424
+ italic: params.italic,
425
+ underline: params.underline
426
+ }
427
+ }
428
+ ]
429
+ };
430
+ slide.elements.push(el);
431
+ break;
432
+ }
433
+ case "shape": {
434
+ const el = {
435
+ id,
436
+ type: "shape",
437
+ x,
438
+ y,
439
+ width,
440
+ height,
441
+ shapeType: params.shapeType ?? "rect",
442
+ text: params.text,
443
+ textStyle: params.text ? {
444
+ fontSize: params.fontSize,
445
+ fontFamily: params.fontFamily,
446
+ color: params.fontColor,
447
+ bold: params.bold,
448
+ italic: params.italic,
449
+ align: params.alignment
450
+ } : void 0,
451
+ shapeStyle: {
452
+ fillColor: params.fillColor,
453
+ strokeColor: params.strokeColor,
454
+ strokeWidth: params.strokeWidth
455
+ }
456
+ };
457
+ slide.elements.push(el);
458
+ break;
459
+ }
460
+ case "image": {
461
+ const el = {
462
+ id,
463
+ type: "image",
464
+ x,
465
+ y,
466
+ width,
467
+ height,
468
+ imageData: params.imageData,
469
+ altText: params.altText
470
+ };
471
+ slide.elements.push(el);
472
+ break;
473
+ }
474
+ case "table": {
475
+ const rowCount = params.rows ?? 2;
476
+ const colCount = params.columns ?? 2;
477
+ const colWidth = 1 / colCount;
478
+ const tableRows = Array.from({ length: rowCount }, (_, r) => ({
479
+ height: 40,
480
+ cells: Array.from({ length: colCount }, (__, c) => ({
481
+ text: params.cellData?.[r]?.[c] ?? ""
482
+ }))
483
+ }));
484
+ const tableEl = {
485
+ id,
486
+ type: "table",
487
+ x,
488
+ y,
489
+ width,
490
+ height: params.height ?? rowCount * 40,
491
+ tableData: {
492
+ rows: tableRows,
493
+ columnWidths: Array.from({ length: colCount }, () => colWidth),
494
+ firstRowHeader: params.headerRow ?? false
495
+ }
496
+ };
497
+ slide.elements.push(tableEl);
498
+ break;
499
+ }
500
+ case "connector": {
501
+ const el = {
502
+ id,
503
+ type: "connector",
504
+ x,
505
+ y,
506
+ width,
507
+ height,
508
+ shapeStyle: {
509
+ strokeColor: params.strokeColor ?? "#000000",
510
+ strokeWidth: params.strokeWidth ?? 1,
511
+ connectorStartArrow: params.startArrow,
512
+ connectorEndArrow: params.endArrow
513
+ }
514
+ };
515
+ slide.elements.push(el);
516
+ break;
517
+ }
518
+ default: {
519
+ throw new Error(`Unknown element type: ${String(params.type)}`);
520
+ }
521
+ }
522
+ return {
523
+ pptxData: ctx.pptxData,
524
+ dirty: true,
525
+ result: { elementId: id, slideIndex: params.slideIndex }
526
+ };
527
+ }
528
+ function updateElement(ctx, params) {
529
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
530
+ if (err) {
531
+ throw new Error(err);
532
+ }
533
+ const slide = ctx.pptxData.slides[params.slideIndex];
534
+ const el = slide.elements.find((e) => e.id === params.elementId);
535
+ if (!el) {
536
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
537
+ }
538
+ if (params.x !== void 0) {
539
+ el.x = params.x;
540
+ }
541
+ if (params.y !== void 0) {
542
+ el.y = params.y;
543
+ }
544
+ if (params.width !== void 0) {
545
+ el.width = params.width;
546
+ }
547
+ if (params.height !== void 0) {
548
+ el.height = params.height;
549
+ }
550
+ if (params.rotation !== void 0) {
551
+ el.rotation = params.rotation;
552
+ }
553
+ if (params.opacity !== void 0) {
554
+ el.opacity = params.opacity;
555
+ }
556
+ if (params.hidden !== void 0) {
557
+ el.hidden = params.hidden;
558
+ }
559
+ if (params.flipH !== void 0) {
560
+ el.flipHorizontal = params.flipH;
561
+ }
562
+ if (params.flipV !== void 0) {
563
+ el.flipVertical = params.flipV;
564
+ }
565
+ if (pptxViewerCore.hasTextProperties(el)) {
566
+ const textEl = el;
567
+ if (params.text !== void 0) {
568
+ textEl.text = params.text;
569
+ if (textEl.textSegments && textEl.textSegments.length > 0) {
570
+ textEl.textSegments[0].text = params.text;
571
+ } else {
572
+ textEl.textSegments = [{ text: params.text, style: {} }];
573
+ }
574
+ }
575
+ if (!textEl.textStyle) {
576
+ textEl.textStyle = {};
577
+ }
578
+ if (params.fontSize !== void 0) {
579
+ textEl.textStyle.fontSize = params.fontSize;
580
+ }
581
+ if (params.fontFamily !== void 0) {
582
+ textEl.textStyle.fontFamily = params.fontFamily;
583
+ }
584
+ if (params.fontColor !== void 0) {
585
+ textEl.textStyle.color = params.fontColor;
586
+ }
587
+ if (params.bold !== void 0) {
588
+ textEl.textStyle.bold = params.bold;
589
+ }
590
+ if (params.italic !== void 0) {
591
+ textEl.textStyle.italic = params.italic;
592
+ }
593
+ if (params.underline !== void 0) {
594
+ textEl.textStyle.underline = params.underline;
595
+ }
596
+ if (params.alignment !== void 0) {
597
+ textEl.textStyle.align = params.alignment;
598
+ }
599
+ }
600
+ if ("shapeStyle" in el && el.shapeStyle) {
601
+ const ss = el.shapeStyle;
602
+ if (params.fillColor !== void 0) {
603
+ ss.fillColor = params.fillColor;
604
+ }
605
+ if (params.strokeColor !== void 0) {
606
+ ss.strokeColor = params.strokeColor;
607
+ }
608
+ if (params.strokeWidth !== void 0) {
609
+ ss.strokeWidth = params.strokeWidth;
610
+ }
611
+ } else if (params.fillColor !== void 0 || params.strokeColor !== void 0 || params.strokeWidth !== void 0) {
612
+ if ("shapeStyle" in el) {
613
+ el.shapeStyle = {
614
+ fillColor: params.fillColor,
615
+ strokeColor: params.strokeColor,
616
+ strokeWidth: params.strokeWidth
617
+ };
618
+ }
619
+ }
620
+ return {
621
+ pptxData: ctx.pptxData,
622
+ dirty: true,
623
+ result: { elementId: params.elementId }
624
+ };
625
+ }
626
+ function deleteElements(ctx, params) {
627
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
628
+ if (err) {
629
+ throw new Error(err);
630
+ }
631
+ const slide = ctx.pptxData.slides[params.slideIndex];
632
+ const idSet = new Set(params.elementIds);
633
+ const notFound = params.elementIds.filter((id) => !slide.elements.some((e) => e.id === id));
634
+ if (notFound.length > 0) {
635
+ throw new Error(`Elements not found: ${notFound.join(", ")}`);
636
+ }
637
+ const before = slide.elements.length;
638
+ slide.elements = slide.elements.filter((e) => !idSet.has(e.id));
639
+ return {
640
+ pptxData: ctx.pptxData,
641
+ dirty: true,
642
+ result: { deletedCount: before - slide.elements.length }
643
+ };
644
+ }
645
+ function arrangeElements(ctx, params) {
646
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
647
+ if (err) {
648
+ throw new Error(err);
649
+ }
650
+ const slide = ctx.pptxData.slides[params.slideIndex];
651
+ if (params.action === "align") {
652
+ if (!params.elementIds || params.elementIds.length === 0) {
653
+ throw new Error("elementIds is required for align action.");
654
+ }
655
+ const targets = slide.elements.filter((e) => params.elementIds.includes(e.id));
656
+ if (targets.length === 0) {
657
+ throw new Error("No matching elements found for align.");
658
+ }
659
+ switch (params.alignment) {
660
+ case "left": {
661
+ const minX = Math.min(...targets.map((e) => e.x));
662
+ for (const t of targets) {
663
+ t.x = minX;
664
+ }
665
+ break;
666
+ }
667
+ case "right": {
668
+ const maxRight = Math.max(...targets.map((e) => e.x + e.width));
669
+ for (const t of targets) {
670
+ t.x = maxRight - t.width;
671
+ }
672
+ break;
673
+ }
674
+ case "top": {
675
+ const minY = Math.min(...targets.map((e) => e.y));
676
+ for (const t of targets) {
677
+ t.y = minY;
678
+ }
679
+ break;
680
+ }
681
+ case "bottom": {
682
+ const maxBottom = Math.max(...targets.map((e) => e.y + e.height));
683
+ for (const t of targets) {
684
+ t.y = maxBottom - t.height;
685
+ }
686
+ break;
687
+ }
688
+ case "centerH": {
689
+ const avgCX = targets.reduce((s, e) => s + e.x + e.width / 2, 0) / targets.length;
690
+ for (const t of targets) {
691
+ t.x = avgCX - t.width / 2;
692
+ }
693
+ break;
694
+ }
695
+ case "centerV": {
696
+ const avgCY = targets.reduce((s, e) => s + e.y + e.height / 2, 0) / targets.length;
697
+ for (const t of targets) {
698
+ t.y = avgCY - t.height / 2;
699
+ }
700
+ break;
701
+ }
702
+ default: {
703
+ throw new Error(`Unknown alignment: ${String(params.alignment)}`);
704
+ }
705
+ }
706
+ } else if (params.action === "reorderLayer") {
707
+ if (!params.elementId) {
708
+ throw new Error("elementId is required for reorderLayer action.");
709
+ }
710
+ const idx = slide.elements.findIndex((e) => e.id === params.elementId);
711
+ if (idx < 0) {
712
+ throw new Error(`Element '${params.elementId}' not found.`);
713
+ }
714
+ const [el] = slide.elements.splice(idx, 1);
715
+ switch (params.layerAction) {
716
+ case "bringToFront":
717
+ slide.elements.push(el);
718
+ break;
719
+ case "sendToBack":
720
+ slide.elements.unshift(el);
721
+ break;
722
+ case "bringForward": {
723
+ const newIdx = Math.min(idx + 1, slide.elements.length);
724
+ slide.elements.splice(newIdx, 0, el);
725
+ break;
726
+ }
727
+ case "sendBackward": {
728
+ const newIdx = Math.max(idx - 1, 0);
729
+ slide.elements.splice(newIdx, 0, el);
730
+ break;
731
+ }
732
+ default: {
733
+ slide.elements.splice(idx, 0, el);
734
+ throw new Error(`Unknown layerAction: ${String(params.layerAction)}`);
735
+ }
736
+ }
737
+ } else {
738
+ throw new Error(`Unknown action: ${String(params.action)}`);
739
+ }
740
+ return {
741
+ pptxData: ctx.pptxData,
742
+ dirty: true,
743
+ result: { slideIndex: params.slideIndex }
744
+ };
745
+ }
746
+ function cloneElement(ctx, params) {
747
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
748
+ if (err) {
749
+ throw new Error(err);
750
+ }
751
+ const srcSlide = ctx.pptxData.slides[params.slideIndex];
752
+ const original = srcSlide.elements.find((e) => e.id === params.elementId);
753
+ if (!original) {
754
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
755
+ }
756
+ const targets = params.targetSlideIndexes ?? [params.slideIndex];
757
+ const clonedIds = [];
758
+ const offsetX = params.offsetX ?? 20;
759
+ const offsetY = params.offsetY ?? 20;
760
+ for (const targetIdx of targets) {
761
+ const terr = validateSlideIndex(targetIdx, ctx.pptxData.slides.length);
762
+ if (terr) {
763
+ throw new Error(terr);
764
+ }
765
+ const clone = structuredClone(original);
766
+ clone.id = generateElementId();
767
+ clone.x += offsetX;
768
+ clone.y += offsetY;
769
+ ctx.pptxData.slides[targetIdx].elements.push(clone);
770
+ clonedIds.push(clone.id);
771
+ }
772
+ return {
773
+ pptxData: ctx.pptxData,
774
+ dirty: true,
775
+ result: { clonedIds }
776
+ };
777
+ }
778
+ function setElementAnimation(ctx, params) {
779
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
780
+ if (err) {
781
+ throw new Error(err);
782
+ }
783
+ const slide = ctx.pptxData.slides[params.slideIndex];
784
+ const elExists = slide.elements.some((e) => e.id === params.elementId);
785
+ if (!elExists) {
786
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
787
+ }
788
+ if (!slide.animations) {
789
+ slide.animations = [];
790
+ }
791
+ const existing = slide.animations.find((a) => a.elementId === params.elementId);
792
+ if (existing) {
793
+ if (params.entrance !== void 0) {
794
+ existing.entrance = params.entrance;
795
+ }
796
+ if (params.exit !== void 0) {
797
+ existing.exit = params.exit;
798
+ }
799
+ if (params.durationMs !== void 0) {
800
+ existing.durationMs = params.durationMs;
801
+ }
802
+ if (params.delayMs !== void 0) {
803
+ existing.delayMs = params.delayMs;
804
+ }
805
+ if (params.order !== void 0) {
806
+ existing.order = params.order;
807
+ }
808
+ } else {
809
+ const anim = {
810
+ elementId: params.elementId,
811
+ entrance: params.entrance,
812
+ exit: params.exit,
813
+ durationMs: params.durationMs,
814
+ delayMs: params.delayMs,
815
+ order: params.order
816
+ };
817
+ slide.animations.push(anim);
818
+ }
819
+ return {
820
+ pptxData: ctx.pptxData,
821
+ dirty: true,
822
+ result: { elementId: params.elementId }
823
+ };
824
+ }
825
+ function groupElements(ctx, params) {
826
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
827
+ if (err) {
828
+ throw new Error(err);
829
+ }
830
+ if (params.elementIds.length < 2) {
831
+ throw new Error("At least 2 elements are required to form a group.");
832
+ }
833
+ const slide = ctx.pptxData.slides[params.slideIndex];
834
+ const idSet = new Set(params.elementIds);
835
+ const children = slide.elements.filter((e) => idSet.has(e.id));
836
+ if (children.length !== params.elementIds.length) {
837
+ const missing = params.elementIds.filter((id) => !slide.elements.some((e) => e.id === id));
838
+ throw new Error(`Elements not found: ${missing.join(", ")}`);
839
+ }
840
+ const minX = Math.min(...children.map((e) => e.x));
841
+ const minY = Math.min(...children.map((e) => e.y));
842
+ const maxX = Math.max(...children.map((e) => e.x + e.width));
843
+ const maxY = Math.max(...children.map((e) => e.y + e.height));
844
+ const localChildren = children.map((e) => {
845
+ const c = structuredClone(e);
846
+ c.x -= minX;
847
+ c.y -= minY;
848
+ return c;
849
+ });
850
+ slide.elements = slide.elements.filter((e) => !idSet.has(e.id));
851
+ const groupId = generateElementId();
852
+ const group = {
853
+ id: groupId,
854
+ type: "group",
855
+ x: minX,
856
+ y: minY,
857
+ width: maxX - minX,
858
+ height: maxY - minY,
859
+ children: localChildren
860
+ };
861
+ slide.elements.push(group);
862
+ return {
863
+ pptxData: ctx.pptxData,
864
+ dirty: true,
865
+ result: { groupId }
866
+ };
867
+ }
868
+ function ungroupElements(ctx, params) {
869
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
870
+ if (err) {
871
+ throw new Error(err);
872
+ }
873
+ const slide = ctx.pptxData.slides[params.slideIndex];
874
+ const groupIdx = slide.elements.findIndex((e) => e.id === params.groupElementId);
875
+ if (groupIdx < 0) {
876
+ throw new Error(`Group element '${params.groupElementId}' not found.`);
877
+ }
878
+ const group = slide.elements[groupIdx];
879
+ if (group.type !== "group") {
880
+ throw new Error(`Element '${params.groupElementId}' is not a group.`);
881
+ }
882
+ const grp = group;
883
+ const restored = grp.children.map((child) => {
884
+ const c = structuredClone(child);
885
+ c.x += grp.x;
886
+ c.y += grp.y;
887
+ return c;
888
+ });
889
+ slide.elements.splice(groupIdx, 1, ...restored);
890
+ return {
891
+ pptxData: ctx.pptxData,
892
+ dirty: true,
893
+ result: { restoredIds: restored.map((c) => c.id) }
894
+ };
895
+ }
896
+ function batchUpdateElements(ctx, params) {
897
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
898
+ if (err) {
899
+ throw new Error(err);
900
+ }
901
+ const slide = ctx.pptxData.slides[params.slideIndex];
902
+ const idSet = new Set(params.elementIds);
903
+ let count = 0;
904
+ for (const el of slide.elements) {
905
+ if (!idSet.has(el.id)) {
906
+ continue;
907
+ }
908
+ if (params.x !== void 0) {
909
+ el.x = params.x;
910
+ }
911
+ if (params.y !== void 0) {
912
+ el.y = params.y;
913
+ }
914
+ if (params.width !== void 0) {
915
+ el.width = params.width;
916
+ }
917
+ if (params.height !== void 0) {
918
+ el.height = params.height;
919
+ }
920
+ if (params.rotation !== void 0) {
921
+ el.rotation = params.rotation;
922
+ }
923
+ if (params.opacity !== void 0) {
924
+ el.opacity = params.opacity;
925
+ }
926
+ if (params.hidden !== void 0) {
927
+ el.hidden = params.hidden;
928
+ }
929
+ count++;
930
+ }
931
+ return {
932
+ pptxData: ctx.pptxData,
933
+ dirty: true,
934
+ result: { updatedCount: count }
935
+ };
936
+ }
937
+
938
+ // src/tools/table-tools.ts
939
+ function updateTableCells(ctx, params) {
940
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
941
+ if (err) {
942
+ throw new Error(err);
943
+ }
944
+ const slide = ctx.pptxData.slides[params.slideIndex];
945
+ const el = slide.elements.find((e) => e.id === params.elementId);
946
+ if (!el) {
947
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
948
+ }
949
+ if (el.type !== "table") {
950
+ throw new Error(`Element '${params.elementId}' is not a table.`);
951
+ }
952
+ const tbl = el;
953
+ if (!tbl.tableData) {
954
+ throw new Error(`Table '${params.elementId}' has no tableData.`);
955
+ }
956
+ let count = 0;
957
+ for (const cellUpdate of params.cells) {
958
+ const row = tbl.tableData.rows[cellUpdate.row];
959
+ if (!row) {
960
+ throw new Error(
961
+ `Row ${cellUpdate.row} out of range (table has ${tbl.tableData.rows.length} rows).`
962
+ );
963
+ }
964
+ const cell = row.cells[cellUpdate.col];
965
+ if (!cell) {
966
+ throw new Error(`Column ${cellUpdate.col} out of range (row has ${row.cells.length} cells).`);
967
+ }
968
+ cell.text = cellUpdate.text;
969
+ count++;
970
+ }
971
+ return {
972
+ pptxData: ctx.pptxData,
973
+ dirty: true,
974
+ result: { updatedCount: count }
975
+ };
976
+ }
977
+ function manageTableStructure(ctx, params) {
978
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
979
+ if (err) {
980
+ throw new Error(err);
981
+ }
982
+ const slide = ctx.pptxData.slides[params.slideIndex];
983
+ const el = slide.elements.find((e) => e.id === params.elementId);
984
+ if (!el) {
985
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
986
+ }
987
+ if (el.type !== "table") {
988
+ throw new Error(`Element '${params.elementId}' is not a table.`);
989
+ }
990
+ const tbl = el;
991
+ if (!tbl.tableData) {
992
+ throw new Error(`Table '${params.elementId}' has no tableData.`);
993
+ }
994
+ const { rows, columnWidths } = tbl.tableData;
995
+ const colCount = columnWidths.length;
996
+ const rowCount = rows.length;
997
+ switch (params.action) {
998
+ case "insertRow": {
999
+ const pos = params.position !== void 0 ? Math.min(Math.max(params.position, 0), rowCount) : rowCount;
1000
+ const newRow = {
1001
+ height: 40,
1002
+ cells: Array.from({ length: colCount }, (_, c) => ({
1003
+ text: params.cellTexts?.[c] ?? ""
1004
+ }))
1005
+ };
1006
+ rows.splice(pos, 0, newRow);
1007
+ break;
1008
+ }
1009
+ case "deleteRow": {
1010
+ if (rowCount <= 1) {
1011
+ throw new Error("Cannot delete the last row of a table.");
1012
+ }
1013
+ const ref = params.referenceIndex ?? rowCount - 1;
1014
+ if (ref < 0 || ref >= rowCount) {
1015
+ throw new Error(`Row index ${ref} out of range (0\u2013${rowCount - 1}).`);
1016
+ }
1017
+ rows.splice(ref, 1);
1018
+ break;
1019
+ }
1020
+ case "insertColumn": {
1021
+ const pos = params.position !== void 0 ? Math.min(Math.max(params.position, 0), colCount) : colCount;
1022
+ const newWidth = 1 / (colCount + 1);
1023
+ const scaleFactor = colCount / (colCount + 1);
1024
+ for (let i = 0; i < columnWidths.length; i++) {
1025
+ columnWidths[i] *= scaleFactor;
1026
+ }
1027
+ columnWidths.splice(pos, 0, newWidth);
1028
+ for (let r = 0; r < rows.length; r++) {
1029
+ rows[r].cells.splice(pos, 0, {
1030
+ text: params.cellTexts?.[r] ?? ""
1031
+ });
1032
+ }
1033
+ break;
1034
+ }
1035
+ case "deleteColumn": {
1036
+ if (colCount <= 1) {
1037
+ throw new Error("Cannot delete the last column of a table.");
1038
+ }
1039
+ const ref = params.referenceIndex ?? colCount - 1;
1040
+ if (ref < 0 || ref >= colCount) {
1041
+ throw new Error(`Column index ${ref} out of range (0\u2013${colCount - 1}).`);
1042
+ }
1043
+ const removedWidth = columnWidths[ref];
1044
+ columnWidths.splice(ref, 1);
1045
+ const remaining = columnWidths.length;
1046
+ if (remaining > 0) {
1047
+ const extra = removedWidth / remaining;
1048
+ for (let i = 0; i < columnWidths.length; i++) {
1049
+ columnWidths[i] += extra;
1050
+ }
1051
+ }
1052
+ for (const row of rows) {
1053
+ row.cells.splice(ref, 1);
1054
+ }
1055
+ break;
1056
+ }
1057
+ default: {
1058
+ throw new Error(`Unknown action: ${String(params.action)}`);
1059
+ }
1060
+ }
1061
+ return {
1062
+ pptxData: ctx.pptxData,
1063
+ dirty: true,
1064
+ result: {
1065
+ rowCount: tbl.tableData.rows.length,
1066
+ columnCount: tbl.tableData.columnWidths.length
1067
+ }
1068
+ };
1069
+ }
1070
+ function updateElementStyle(ctx, params) {
1071
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1072
+ if (err) {
1073
+ throw new Error(err);
1074
+ }
1075
+ const slide = ctx.pptxData.slides[params.slideIndex];
1076
+ const el = slide.elements.find((e) => e.id === params.elementId);
1077
+ if (!el) {
1078
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
1079
+ }
1080
+ if ("shapeStyle" in el) {
1081
+ if (!el.shapeStyle) {
1082
+ el.shapeStyle = {};
1083
+ }
1084
+ const ss = el.shapeStyle;
1085
+ if (params.fillColor !== void 0) {
1086
+ ss.fillColor = params.fillColor;
1087
+ }
1088
+ if (params.fillMode !== void 0) {
1089
+ ss.fillMode = params.fillMode;
1090
+ }
1091
+ if (params.fillGradientStops !== void 0) {
1092
+ ss.fillGradientStops = params.fillGradientStops;
1093
+ }
1094
+ if (params.fillGradientAngle !== void 0) {
1095
+ ss.fillGradientAngle = params.fillGradientAngle;
1096
+ }
1097
+ if (params.fillGradientType !== void 0) {
1098
+ ss.fillGradientType = params.fillGradientType;
1099
+ }
1100
+ if (params.fillOpacity !== void 0) {
1101
+ ss.fillOpacity = params.fillOpacity;
1102
+ }
1103
+ if (params.strokeColor !== void 0) {
1104
+ ss.strokeColor = params.strokeColor;
1105
+ }
1106
+ if (params.strokeWidth !== void 0) {
1107
+ ss.strokeWidth = params.strokeWidth;
1108
+ }
1109
+ if (params.strokeDash !== void 0) {
1110
+ ss.strokeDash = params.strokeDash;
1111
+ }
1112
+ if (params.strokeOpacity !== void 0) {
1113
+ ss.strokeOpacity = params.strokeOpacity;
1114
+ }
1115
+ if (params.shadowColor !== void 0) {
1116
+ ss.shadowColor = params.shadowColor;
1117
+ }
1118
+ if (params.shadowBlur !== void 0) {
1119
+ ss.shadowBlur = params.shadowBlur;
1120
+ }
1121
+ if (params.shadowOffsetX !== void 0) {
1122
+ ss.shadowOffsetX = params.shadowOffsetX;
1123
+ }
1124
+ if (params.shadowOffsetY !== void 0) {
1125
+ ss.shadowOffsetY = params.shadowOffsetY;
1126
+ }
1127
+ if (params.shadowOpacity !== void 0) {
1128
+ ss.shadowOpacity = params.shadowOpacity;
1129
+ }
1130
+ if (params.glowColor !== void 0) {
1131
+ ss.glowColor = params.glowColor;
1132
+ }
1133
+ if (params.glowRadius !== void 0) {
1134
+ ss.glowRadius = params.glowRadius;
1135
+ }
1136
+ if (params.glowOpacity !== void 0) {
1137
+ ss.glowOpacity = params.glowOpacity;
1138
+ }
1139
+ if (params.softEdgeRadius !== void 0) {
1140
+ ss.softEdgeRadius = params.softEdgeRadius;
1141
+ }
1142
+ }
1143
+ if (el.type === "image" || el.type === "picture") {
1144
+ const img = el;
1145
+ if (params.altText !== void 0) {
1146
+ img.altText = params.altText;
1147
+ }
1148
+ if (params.cropLeft !== void 0) {
1149
+ img.cropLeft = params.cropLeft;
1150
+ }
1151
+ if (params.cropTop !== void 0) {
1152
+ img.cropTop = params.cropTop;
1153
+ }
1154
+ if (params.cropRight !== void 0) {
1155
+ img.cropRight = params.cropRight;
1156
+ }
1157
+ if (params.cropBottom !== void 0) {
1158
+ img.cropBottom = params.cropBottom;
1159
+ }
1160
+ if (params.brightness !== void 0 || params.contrast !== void 0 || params.grayscale !== void 0) {
1161
+ if (!img.imageEffects) {
1162
+ img.imageEffects = {};
1163
+ }
1164
+ if (params.brightness !== void 0) {
1165
+ img.imageEffects.brightness = params.brightness;
1166
+ }
1167
+ if (params.contrast !== void 0) {
1168
+ img.imageEffects.contrast = params.contrast;
1169
+ }
1170
+ if (params.grayscale !== void 0) {
1171
+ img.imageEffects.grayscale = params.grayscale;
1172
+ }
1173
+ }
1174
+ }
1175
+ return {
1176
+ pptxData: ctx.pptxData,
1177
+ dirty: true,
1178
+ result: { elementId: params.elementId }
1179
+ };
1180
+ }
1181
+ function runAccessibilityCheck(ctx) {
1182
+ const issues = [];
1183
+ for (let si = 0; si < ctx.pptxData.slides.length; si++) {
1184
+ const slide = ctx.pptxData.slides[si];
1185
+ let slideHasText = false;
1186
+ for (const el of slide.elements) {
1187
+ if (el.type === "image" || el.type === "picture") {
1188
+ const img = el;
1189
+ if (!img.altText) {
1190
+ issues.push({
1191
+ slideIndex: si,
1192
+ elementId: el.id,
1193
+ severity: "error",
1194
+ message: `Image element '${el.id}' has no alt text.`
1195
+ });
1196
+ }
1197
+ }
1198
+ if (pptxViewerCore.hasTextProperties(el)) {
1199
+ const textEl = el;
1200
+ slideHasText = true;
1201
+ if (!textEl.text || textEl.text.trim() === "") {
1202
+ issues.push({
1203
+ slideIndex: si,
1204
+ elementId: el.id,
1205
+ severity: "info",
1206
+ message: `Text element '${el.id}' is empty.`
1207
+ });
1208
+ }
1209
+ const fs = textEl.textStyle?.fontSize;
1210
+ if (fs !== void 0 && fs < 10) {
1211
+ issues.push({
1212
+ slideIndex: si,
1213
+ elementId: el.id,
1214
+ severity: "warning",
1215
+ message: `Text element '${el.id}' has very small font size (${fs}pt < 10pt).`
1216
+ });
1217
+ }
1218
+ const textColor = textEl.textStyle?.color?.toLowerCase();
1219
+ const bgColor = slide.backgroundColor?.toLowerCase();
1220
+ if (textColor && bgColor && textColor === bgColor) {
1221
+ issues.push({
1222
+ slideIndex: si,
1223
+ elementId: el.id,
1224
+ severity: "error",
1225
+ message: `Text element '${el.id}' has same color as slide background (${textColor}).`
1226
+ });
1227
+ }
1228
+ }
1229
+ }
1230
+ if (!slideHasText) {
1231
+ issues.push({
1232
+ slideIndex: si,
1233
+ severity: "warning",
1234
+ message: `Slide ${si + 1} contains no text elements.`
1235
+ });
1236
+ }
1237
+ }
1238
+ const errorCount = issues.filter((i) => i.severity === "error").length;
1239
+ const warningCount = issues.filter((i) => i.severity === "warning").length;
1240
+ const infoCount = issues.filter((i) => i.severity === "info").length;
1241
+ return {
1242
+ pptxData: ctx.pptxData,
1243
+ dirty: false,
1244
+ result: {
1245
+ slideCount: ctx.pptxData.slides.length,
1246
+ totalIssues: issues.length,
1247
+ errorCount,
1248
+ warningCount,
1249
+ infoCount,
1250
+ issues
1251
+ }
1252
+ };
1253
+ }
1254
+ function buildSearchRegex(query, useRegex, caseSensitive) {
1255
+ const pattern = useRegex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1256
+ const flags = caseSensitive ? "g" : "gi";
1257
+ return new RegExp(pattern, flags);
1258
+ }
1259
+ function findText(ctx, params) {
1260
+ const regex = buildSearchRegex(
1261
+ params.query,
1262
+ params.useRegex === true,
1263
+ params.caseSensitive === true
1264
+ );
1265
+ const targetIndexes = params.slideIndexes ? params.slideIndexes : ctx.pptxData.slides.map((_, i) => i);
1266
+ const matches = [];
1267
+ for (const si of targetIndexes) {
1268
+ const err = validateSlideIndex(si, ctx.pptxData.slides.length);
1269
+ if (err) {
1270
+ throw new Error(err);
1271
+ }
1272
+ const slide = ctx.pptxData.slides[si];
1273
+ for (const el of slide.elements) {
1274
+ if (pptxViewerCore.hasTextProperties(el)) {
1275
+ const textEl = el;
1276
+ if (textEl.text) {
1277
+ const localMatches = Array.from(textEl.text.matchAll(regex));
1278
+ for (const m of localMatches) {
1279
+ matches.push({
1280
+ slideIndex: si,
1281
+ elementId: el.id,
1282
+ elementType: el.type,
1283
+ matchedText: m[0],
1284
+ context: textEl.text.slice(
1285
+ Math.max(0, (m.index ?? 0) - 20),
1286
+ (m.index ?? 0) + m[0].length + 20
1287
+ )
1288
+ });
1289
+ }
1290
+ }
1291
+ }
1292
+ if (el.type === "table") {
1293
+ const tbl = el;
1294
+ if (tbl.tableData) {
1295
+ for (const row of tbl.tableData.rows) {
1296
+ for (const cell of row.cells) {
1297
+ if (cell.text) {
1298
+ const localMatches = Array.from(cell.text.matchAll(regex));
1299
+ for (const m of localMatches) {
1300
+ matches.push({
1301
+ slideIndex: si,
1302
+ elementId: el.id,
1303
+ elementType: "table-cell",
1304
+ matchedText: m[0],
1305
+ context: cell.text.slice(
1306
+ Math.max(0, (m.index ?? 0) - 20),
1307
+ (m.index ?? 0) + m[0].length + 20
1308
+ )
1309
+ });
1310
+ }
1311
+ }
1312
+ }
1313
+ }
1314
+ }
1315
+ }
1316
+ }
1317
+ if (slide.notes) {
1318
+ const noteMatches = Array.from(slide.notes.matchAll(regex));
1319
+ for (const m of noteMatches) {
1320
+ matches.push({
1321
+ slideIndex: si,
1322
+ elementId: "notes",
1323
+ elementType: "notes",
1324
+ matchedText: m[0],
1325
+ context: slide.notes.slice(
1326
+ Math.max(0, (m.index ?? 0) - 20),
1327
+ (m.index ?? 0) + m[0].length + 20
1328
+ )
1329
+ });
1330
+ }
1331
+ }
1332
+ }
1333
+ return {
1334
+ pptxData: ctx.pptxData,
1335
+ dirty: false,
1336
+ result: {
1337
+ query: params.query,
1338
+ matchCount: matches.length,
1339
+ matches
1340
+ }
1341
+ };
1342
+ }
1343
+ function replaceText(ctx, params) {
1344
+ const regex = buildSearchRegex(
1345
+ params.query,
1346
+ params.useRegex === true,
1347
+ params.caseSensitive === true
1348
+ );
1349
+ const targetIndexes = params.slideIndexes ? params.slideIndexes : ctx.pptxData.slides.map((_, i) => i);
1350
+ let count = 0;
1351
+ for (const si of targetIndexes) {
1352
+ const err = validateSlideIndex(si, ctx.pptxData.slides.length);
1353
+ if (err) {
1354
+ throw new Error(err);
1355
+ }
1356
+ const slide = ctx.pptxData.slides[si];
1357
+ for (const el of slide.elements) {
1358
+ if (pptxViewerCore.hasTextProperties(el)) {
1359
+ const textEl = el;
1360
+ if (textEl.text) {
1361
+ const before = textEl.text;
1362
+ const after = before.replace(regex, params.replacement);
1363
+ if (after !== before) {
1364
+ const matchCount = Array.from(before.matchAll(regex)).length;
1365
+ count += matchCount;
1366
+ textEl.text = after;
1367
+ if (textEl.textSegments && textEl.textSegments.length > 0) {
1368
+ const firstSeg = textEl.textSegments[0];
1369
+ const updatedFirst = firstSeg.text.replace(regex, params.replacement);
1370
+ if (updatedFirst !== firstSeg.text) {
1371
+ firstSeg.text = updatedFirst;
1372
+ } else {
1373
+ textEl.textSegments = [{ text: after, style: firstSeg.style }];
1374
+ }
1375
+ }
1376
+ }
1377
+ }
1378
+ }
1379
+ if (el.type === "table") {
1380
+ const tbl = el;
1381
+ if (tbl.tableData) {
1382
+ for (const row of tbl.tableData.rows) {
1383
+ for (const cell of row.cells) {
1384
+ if (cell.text) {
1385
+ const before = cell.text;
1386
+ const after = before.replace(regex, params.replacement);
1387
+ if (after !== before) {
1388
+ count += Array.from(before.matchAll(regex)).length;
1389
+ cell.text = after;
1390
+ }
1391
+ }
1392
+ }
1393
+ }
1394
+ }
1395
+ }
1396
+ }
1397
+ if (slide.notes) {
1398
+ const before = slide.notes;
1399
+ const after = before.replace(regex, params.replacement);
1400
+ if (after !== before) {
1401
+ count += Array.from(before.matchAll(regex)).length;
1402
+ slide.notes = after;
1403
+ }
1404
+ }
1405
+ }
1406
+ return {
1407
+ pptxData: ctx.pptxData,
1408
+ dirty: count > 0,
1409
+ result: {
1410
+ query: params.query,
1411
+ replacement: params.replacement,
1412
+ replacementCount: count
1413
+ }
1414
+ };
1415
+ }
1416
+ function manageComments(ctx, params) {
1417
+ switch (params.action) {
1418
+ case "list": {
1419
+ const all = [];
1420
+ for (let si = 0; si < ctx.pptxData.slides.length; si++) {
1421
+ if (params.slideIndex !== void 0 && si !== params.slideIndex) {
1422
+ continue;
1423
+ }
1424
+ const slide = ctx.pptxData.slides[si];
1425
+ for (const c of slide.comments ?? []) {
1426
+ if (!params.includeResolved && c.resolved) {
1427
+ continue;
1428
+ }
1429
+ all.push({
1430
+ id: c.id,
1431
+ slideIndex: si,
1432
+ text: c.text,
1433
+ author: c.author,
1434
+ createdAt: c.createdAt,
1435
+ resolved: c.resolved
1436
+ });
1437
+ }
1438
+ }
1439
+ return {
1440
+ pptxData: ctx.pptxData,
1441
+ dirty: false,
1442
+ result: { action: "list", comments: all, count: all.length }
1443
+ };
1444
+ }
1445
+ case "add": {
1446
+ if (params.slideIndex === void 0) {
1447
+ throw new Error("slideIndex is required for add action.");
1448
+ }
1449
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1450
+ if (err) {
1451
+ throw new Error(err);
1452
+ }
1453
+ if (!params.text) {
1454
+ throw new Error("text is required for add action.");
1455
+ }
1456
+ const slide = ctx.pptxData.slides[params.slideIndex];
1457
+ if (!slide.comments) {
1458
+ slide.comments = [];
1459
+ }
1460
+ const newId = `comment-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1461
+ slide.comments.push({
1462
+ id: newId,
1463
+ text: params.text,
1464
+ author: params.author,
1465
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1466
+ resolved: false
1467
+ });
1468
+ return {
1469
+ pptxData: ctx.pptxData,
1470
+ dirty: true,
1471
+ result: { action: "add", commentId: newId }
1472
+ };
1473
+ }
1474
+ case "delete": {
1475
+ if (!params.commentId) {
1476
+ throw new Error("commentId is required for delete action.");
1477
+ }
1478
+ let deleted = false;
1479
+ for (const slide of ctx.pptxData.slides) {
1480
+ if (!slide.comments) {
1481
+ continue;
1482
+ }
1483
+ const before = slide.comments.length;
1484
+ slide.comments = slide.comments.filter((c) => c.id !== params.commentId);
1485
+ if (slide.comments.length < before) {
1486
+ deleted = true;
1487
+ break;
1488
+ }
1489
+ }
1490
+ if (!deleted) {
1491
+ throw new Error(`Comment '${params.commentId}' not found.`);
1492
+ }
1493
+ return {
1494
+ pptxData: ctx.pptxData,
1495
+ dirty: true,
1496
+ result: { action: "delete", commentId: params.commentId }
1497
+ };
1498
+ }
1499
+ case "resolve": {
1500
+ if (!params.commentId) {
1501
+ throw new Error("commentId is required for resolve action.");
1502
+ }
1503
+ let found = false;
1504
+ for (const slide of ctx.pptxData.slides) {
1505
+ const comment = slide.comments?.find((c) => c.id === params.commentId);
1506
+ if (comment) {
1507
+ comment.resolved = params.resolved !== false;
1508
+ found = true;
1509
+ break;
1510
+ }
1511
+ }
1512
+ if (!found) {
1513
+ throw new Error(`Comment '${params.commentId}' not found.`);
1514
+ }
1515
+ return {
1516
+ pptxData: ctx.pptxData,
1517
+ dirty: true,
1518
+ result: { action: "resolve", commentId: params.commentId }
1519
+ };
1520
+ }
1521
+ default: {
1522
+ throw new Error(`Unknown action: ${String(params.action)}`);
1523
+ }
1524
+ }
1525
+ }
1526
+ async function convertToMarkdown(ctx, params) {
1527
+ const options = {
1528
+ mediaFolderName: params.mediaFolderName ?? "media",
1529
+ includeMetadata: params.includeMetadata !== false,
1530
+ slideRange: params.slideRange,
1531
+ includeSpeakerNotes: params.includeSpeakerNotes !== false,
1532
+ semanticMode: params.semanticMode === true,
1533
+ sourceName: params.sourceName ?? "unknown"
1534
+ };
1535
+ const converter = new pptxViewerCore.PptxMarkdownConverter(params.outputDir ?? ".", options);
1536
+ const markdown = await converter.convert(ctx.pptxData);
1537
+ return {
1538
+ pptxData: ctx.pptxData,
1539
+ dirty: false,
1540
+ result: {
1541
+ markdown,
1542
+ slidesConverted: converter.slidesConverted,
1543
+ totalSlides: converter.presentationSlides,
1544
+ imagesExtracted: converter.imagesExtracted
1545
+ }
1546
+ };
1547
+ }
1548
+
1549
+ exports.addElement = addElement;
1550
+ exports.addSlide = addSlide;
1551
+ exports.arrangeElements = arrangeElements;
1552
+ exports.batchUpdateElements = batchUpdateElements;
1553
+ exports.cloneElement = cloneElement;
1554
+ exports.convertToMarkdown = convertToMarkdown;
1555
+ exports.deleteElements = deleteElements;
1556
+ exports.deleteSlides = deleteSlides;
1557
+ exports.describeElement = describeElement;
1558
+ exports.duplicateSlide = duplicateSlide;
1559
+ exports.executeToolWithContext = executeToolWithContext;
1560
+ exports.extractSlideText = extractSlideText;
1561
+ exports.findText = findText;
1562
+ exports.generateElementId = generateElementId;
1563
+ exports.generateSlideId = generateSlideId;
1564
+ exports.getSlide = getSlide;
1565
+ exports.groupElements = groupElements;
1566
+ exports.loadPresentation = loadPresentation;
1567
+ exports.manageComments = manageComments;
1568
+ exports.manageTableStructure = manageTableStructure;
1569
+ exports.reorderSlides = reorderSlides;
1570
+ exports.replaceText = replaceText;
1571
+ exports.runAccessibilityCheck = runAccessibilityCheck;
1572
+ exports.savePresentation = savePresentation;
1573
+ exports.setCanvasSize = setCanvasSize;
1574
+ exports.setElementAnimation = setElementAnimation;
1575
+ exports.setSlideTransition = setSlideTransition;
1576
+ exports.ungroupElements = ungroupElements;
1577
+ exports.updateElement = updateElement;
1578
+ exports.updateElementStyle = updateElementStyle;
1579
+ exports.updateSlideProperties = updateSlideProperties;
1580
+ exports.updateTableCells = updateTableCells;
1581
+ exports.validateSlideIndex = validateSlideIndex;