mcpcn-office-powerpoint-mcp-server 2.1.1__py3-none-any.whl → 2.1.2__py3-none-any.whl
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.
- {mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info → mcpcn_office_powerpoint_mcp_server-2.1.2.dist-info}/METADATA +82 -30
- mcpcn_office_powerpoint_mcp_server-2.1.2.dist-info/RECORD +25 -0
- {mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info → mcpcn_office_powerpoint_mcp_server-2.1.2.dist-info}/WHEEL +1 -1
- {mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info → mcpcn_office_powerpoint_mcp_server-2.1.2.dist-info}/licenses/LICENSE +20 -20
- ppt_mcp_server.py +474 -474
- slide_layout_templates.json +3689 -3689
- tools/__init__.py +27 -27
- tools/chart_tools.py +81 -81
- tools/connector_tools.py +90 -90
- tools/content_tools.py +966 -778
- tools/hyperlink_tools.py +137 -137
- tools/master_tools.py +113 -113
- tools/presentation_tools.py +211 -211
- tools/professional_tools.py +289 -289
- tools/structural_tools.py +372 -372
- tools/template_tools.py +520 -520
- tools/transition_tools.py +74 -74
- utils/__init__.py +69 -68
- utils/content_utils.py +633 -578
- utils/core_utils.py +54 -54
- utils/design_utils.py +688 -688
- utils/presentation_utils.py +216 -216
- utils/template_utils.py +1142 -1142
- utils/validation_utils.py +322 -322
- mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info/RECORD +0 -25
- {mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info → mcpcn_office_powerpoint_mcp_server-2.1.2.dist-info}/entry_points.txt +0 -0
utils/content_utils.py
CHANGED
|
@@ -1,579 +1,634 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Content management utilities for PowerPoint MCP Server.
|
|
3
|
-
Functions for slides, text, images, tables, charts, and shapes.
|
|
4
|
-
"""
|
|
5
|
-
from pptx import Presentation
|
|
6
|
-
from pptx.chart.data import CategoryChartData
|
|
7
|
-
from pptx.enum.chart import XL_CHART_TYPE
|
|
8
|
-
from pptx.enum.text import PP_ALIGN
|
|
9
|
-
from pptx.util import Inches, Pt
|
|
10
|
-
from pptx.dml.color import RGBColor
|
|
11
|
-
from typing import Dict, List, Tuple, Optional, Any
|
|
12
|
-
import tempfile
|
|
13
|
-
import os
|
|
14
|
-
import base64
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def add_slide(presentation: Presentation, layout_index: int = 1) -> Tuple:
|
|
18
|
-
"""
|
|
19
|
-
Add a slide to the presentation.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
presentation: The Presentation object
|
|
23
|
-
layout_index: Index of the slide layout to use
|
|
24
|
-
|
|
25
|
-
Returns:
|
|
26
|
-
A tuple containing the slide and its layout
|
|
27
|
-
"""
|
|
28
|
-
layout = presentation.slide_layouts[layout_index]
|
|
29
|
-
slide = presentation.slides.add_slide(layout)
|
|
30
|
-
return slide, layout
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
for
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
except
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
1
|
+
"""
|
|
2
|
+
Content management utilities for PowerPoint MCP Server.
|
|
3
|
+
Functions for slides, text, images, tables, charts, and shapes.
|
|
4
|
+
"""
|
|
5
|
+
from pptx import Presentation
|
|
6
|
+
from pptx.chart.data import CategoryChartData
|
|
7
|
+
from pptx.enum.chart import XL_CHART_TYPE
|
|
8
|
+
from pptx.enum.text import PP_ALIGN
|
|
9
|
+
from pptx.util import Inches, Pt
|
|
10
|
+
from pptx.dml.color import RGBColor
|
|
11
|
+
from typing import Dict, List, Tuple, Optional, Any
|
|
12
|
+
import tempfile
|
|
13
|
+
import os
|
|
14
|
+
import base64
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def add_slide(presentation: Presentation, layout_index: int = 1) -> Tuple:
|
|
18
|
+
"""
|
|
19
|
+
Add a slide to the presentation.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
presentation: The Presentation object
|
|
23
|
+
layout_index: Index of the slide layout to use
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A tuple containing the slide and its layout
|
|
27
|
+
"""
|
|
28
|
+
layout = presentation.slide_layouts[layout_index]
|
|
29
|
+
slide = presentation.slides.add_slide(layout)
|
|
30
|
+
return slide, layout
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def move_slide(presentation: Presentation, from_index: int, to_index: int) -> Dict:
|
|
34
|
+
"""
|
|
35
|
+
Move a slide from one position to another in the presentation.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
presentation: The Presentation object
|
|
39
|
+
from_index: Current index of the slide to move (0-based)
|
|
40
|
+
to_index: Target index where the slide should be moved (0-based)
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Dictionary with operation result
|
|
44
|
+
"""
|
|
45
|
+
slides = presentation.slides
|
|
46
|
+
slide_count = len(slides)
|
|
47
|
+
|
|
48
|
+
# Validate indices
|
|
49
|
+
if from_index < 0 or from_index >= slide_count:
|
|
50
|
+
raise IndexError(f"Invalid from_index: {from_index}. Valid range: 0-{slide_count - 1}")
|
|
51
|
+
|
|
52
|
+
if to_index < 0 or to_index >= slide_count:
|
|
53
|
+
raise IndexError(f"Invalid to_index: {to_index}. Valid range: 0-{slide_count - 1}")
|
|
54
|
+
|
|
55
|
+
if from_index == to_index:
|
|
56
|
+
return {
|
|
57
|
+
"success": True,
|
|
58
|
+
"message": "Slide is already at the target position",
|
|
59
|
+
"from_index": from_index,
|
|
60
|
+
"to_index": to_index
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
# Access the slide id list element in the underlying XML
|
|
65
|
+
# The slides part contains sldIdLst which maintains slide order
|
|
66
|
+
sldIdLst = presentation.slides._sldIdLst
|
|
67
|
+
|
|
68
|
+
# Get the sldId element for the slide we want to move
|
|
69
|
+
sldId = sldIdLst[from_index]
|
|
70
|
+
|
|
71
|
+
# Remove the slide ID from its current position
|
|
72
|
+
sldIdLst.remove(sldId)
|
|
73
|
+
|
|
74
|
+
# Insert the slide ID at the new position
|
|
75
|
+
sldIdLst.insert(to_index, sldId)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"success": True,
|
|
79
|
+
"message": f"Successfully moved slide from position {from_index} to position {to_index}",
|
|
80
|
+
"from_index": from_index,
|
|
81
|
+
"to_index": to_index,
|
|
82
|
+
"total_slides": slide_count
|
|
83
|
+
}
|
|
84
|
+
except Exception as e:
|
|
85
|
+
raise Exception(f"Failed to move slide: {str(e)}")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_slide_info(slide, slide_index: int) -> Dict:
|
|
89
|
+
"""
|
|
90
|
+
Get information about a specific slide.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
slide: The slide object
|
|
94
|
+
slide_index: Index of the slide
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Dictionary containing slide information
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
placeholders = []
|
|
101
|
+
for placeholder in slide.placeholders:
|
|
102
|
+
placeholder_info = {
|
|
103
|
+
"idx": placeholder.placeholder_format.idx,
|
|
104
|
+
"type": str(placeholder.placeholder_format.type),
|
|
105
|
+
"name": placeholder.name
|
|
106
|
+
}
|
|
107
|
+
placeholders.append(placeholder_info)
|
|
108
|
+
|
|
109
|
+
shapes = []
|
|
110
|
+
for i, shape in enumerate(slide.shapes):
|
|
111
|
+
shape_info = {
|
|
112
|
+
"index": i,
|
|
113
|
+
"name": shape.name,
|
|
114
|
+
"shape_type": str(shape.shape_type),
|
|
115
|
+
"left": shape.left,
|
|
116
|
+
"top": shape.top,
|
|
117
|
+
"width": shape.width,
|
|
118
|
+
"height": shape.height
|
|
119
|
+
}
|
|
120
|
+
shapes.append(shape_info)
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"slide_index": slide_index,
|
|
124
|
+
"layout_name": slide.slide_layout.name,
|
|
125
|
+
"placeholder_count": len(placeholders),
|
|
126
|
+
"placeholders": placeholders,
|
|
127
|
+
"shape_count": len(shapes),
|
|
128
|
+
"shapes": shapes
|
|
129
|
+
}
|
|
130
|
+
except Exception as e:
|
|
131
|
+
raise Exception(f"Failed to get slide info: {str(e)}")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def set_title(slide, title: str) -> None:
|
|
135
|
+
"""
|
|
136
|
+
Set the title of a slide.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
slide: The slide object
|
|
140
|
+
title: The title text
|
|
141
|
+
"""
|
|
142
|
+
if slide.shapes.title:
|
|
143
|
+
slide.shapes.title.text = title
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def populate_placeholder(slide, placeholder_idx: int, text: str) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Populate a placeholder with text.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
slide: The slide object
|
|
152
|
+
placeholder_idx: The index of the placeholder
|
|
153
|
+
text: The text to add
|
|
154
|
+
"""
|
|
155
|
+
placeholder = slide.placeholders[placeholder_idx]
|
|
156
|
+
placeholder.text = text
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def add_bullet_points(placeholder, bullet_points: List[str]) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Add bullet points to a placeholder.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
placeholder: The placeholder object
|
|
165
|
+
bullet_points: List of bullet point texts
|
|
166
|
+
"""
|
|
167
|
+
text_frame = placeholder.text_frame
|
|
168
|
+
text_frame.clear()
|
|
169
|
+
|
|
170
|
+
for i, point in enumerate(bullet_points):
|
|
171
|
+
p = text_frame.add_paragraph()
|
|
172
|
+
p.text = point
|
|
173
|
+
p.level = 0
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def add_textbox(slide, left: float, top: float, width: float, height: float, text: str,
|
|
177
|
+
font_size: int = None, font_name: str = None, bold: bool = None,
|
|
178
|
+
italic: bool = None, underline: bool = None,
|
|
179
|
+
color: Tuple[int, int, int] = None, bg_color: Tuple[int, int, int] = None,
|
|
180
|
+
alignment: str = None, vertical_alignment: str = None,
|
|
181
|
+
auto_fit: bool = True) -> Any:
|
|
182
|
+
"""
|
|
183
|
+
Add a textbox to a slide with formatting options.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
slide: The slide object
|
|
187
|
+
left: Left position in inches
|
|
188
|
+
top: Top position in inches
|
|
189
|
+
width: Width in inches
|
|
190
|
+
height: Height in inches
|
|
191
|
+
text: Text content
|
|
192
|
+
font_size: Font size in points
|
|
193
|
+
font_name: Font name
|
|
194
|
+
bold: Whether text should be bold
|
|
195
|
+
italic: Whether text should be italic
|
|
196
|
+
underline: Whether text should be underlined
|
|
197
|
+
color: RGB color tuple (r, g, b)
|
|
198
|
+
bg_color: Background RGB color tuple (r, g, b)
|
|
199
|
+
alignment: Text alignment ('left', 'center', 'right', 'justify')
|
|
200
|
+
vertical_alignment: Vertical alignment ('top', 'middle', 'bottom')
|
|
201
|
+
auto_fit: Whether to auto-fit text
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
The created textbox shape
|
|
205
|
+
"""
|
|
206
|
+
textbox = slide.shapes.add_textbox(
|
|
207
|
+
Inches(left), Inches(top), Inches(width), Inches(height)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
textbox.text_frame.text = text
|
|
211
|
+
|
|
212
|
+
# Apply formatting if provided
|
|
213
|
+
if any([font_size, font_name, bold, italic, underline, color, bg_color, alignment, vertical_alignment]):
|
|
214
|
+
format_text_advanced(
|
|
215
|
+
textbox.text_frame,
|
|
216
|
+
font_size=font_size,
|
|
217
|
+
font_name=font_name,
|
|
218
|
+
bold=bold,
|
|
219
|
+
italic=italic,
|
|
220
|
+
underline=underline,
|
|
221
|
+
color=color,
|
|
222
|
+
bg_color=bg_color,
|
|
223
|
+
alignment=alignment,
|
|
224
|
+
vertical_alignment=vertical_alignment
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return textbox
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def format_text(text_frame, font_size: int = None, font_name: str = None,
|
|
231
|
+
bold: bool = None, italic: bool = None, color: Tuple[int, int, int] = None,
|
|
232
|
+
alignment: str = None) -> None:
|
|
233
|
+
"""
|
|
234
|
+
Format text in a text frame.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
text_frame: The text frame to format
|
|
238
|
+
font_size: Font size in points
|
|
239
|
+
font_name: Font name
|
|
240
|
+
bold: Whether text should be bold
|
|
241
|
+
italic: Whether text should be italic
|
|
242
|
+
color: RGB color tuple (r, g, b)
|
|
243
|
+
alignment: Text alignment ('left', 'center', 'right', 'justify')
|
|
244
|
+
"""
|
|
245
|
+
alignment_map = {
|
|
246
|
+
'left': PP_ALIGN.LEFT,
|
|
247
|
+
'center': PP_ALIGN.CENTER,
|
|
248
|
+
'right': PP_ALIGN.RIGHT,
|
|
249
|
+
'justify': PP_ALIGN.JUSTIFY
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for paragraph in text_frame.paragraphs:
|
|
253
|
+
if alignment and alignment in alignment_map:
|
|
254
|
+
paragraph.alignment = alignment_map[alignment]
|
|
255
|
+
|
|
256
|
+
for run in paragraph.runs:
|
|
257
|
+
font = run.font
|
|
258
|
+
|
|
259
|
+
if font_size is not None:
|
|
260
|
+
font.size = Pt(font_size)
|
|
261
|
+
if font_name is not None:
|
|
262
|
+
font.name = font_name
|
|
263
|
+
if bold is not None:
|
|
264
|
+
font.bold = bold
|
|
265
|
+
if italic is not None:
|
|
266
|
+
font.italic = italic
|
|
267
|
+
if color is not None:
|
|
268
|
+
r, g, b = color
|
|
269
|
+
font.color.rgb = RGBColor(r, g, b)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def format_text_advanced(text_frame, font_size: int = None, font_name: str = None,
|
|
273
|
+
bold: bool = None, italic: bool = None, underline: bool = None,
|
|
274
|
+
color: Tuple[int, int, int] = None, bg_color: Tuple[int, int, int] = None,
|
|
275
|
+
alignment: str = None, vertical_alignment: str = None) -> Dict:
|
|
276
|
+
"""
|
|
277
|
+
Advanced text formatting with comprehensive options.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
text_frame: The text frame to format
|
|
281
|
+
font_size: Font size in points
|
|
282
|
+
font_name: Font name
|
|
283
|
+
bold: Whether text should be bold
|
|
284
|
+
italic: Whether text should be italic
|
|
285
|
+
underline: Whether text should be underlined
|
|
286
|
+
color: RGB color tuple (r, g, b)
|
|
287
|
+
bg_color: Background RGB color tuple (r, g, b)
|
|
288
|
+
alignment: Text alignment ('left', 'center', 'right', 'justify')
|
|
289
|
+
vertical_alignment: Vertical alignment ('top', 'middle', 'bottom')
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Dictionary with formatting results
|
|
293
|
+
"""
|
|
294
|
+
result = {
|
|
295
|
+
'success': True,
|
|
296
|
+
'warnings': []
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
alignment_map = {
|
|
301
|
+
'left': PP_ALIGN.LEFT,
|
|
302
|
+
'center': PP_ALIGN.CENTER,
|
|
303
|
+
'right': PP_ALIGN.RIGHT,
|
|
304
|
+
'justify': PP_ALIGN.JUSTIFY
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# Enable text wrapping
|
|
308
|
+
text_frame.word_wrap = True
|
|
309
|
+
|
|
310
|
+
# Apply formatting to all paragraphs and runs
|
|
311
|
+
for paragraph in text_frame.paragraphs:
|
|
312
|
+
if alignment and alignment in alignment_map:
|
|
313
|
+
paragraph.alignment = alignment_map[alignment]
|
|
314
|
+
|
|
315
|
+
for run in paragraph.runs:
|
|
316
|
+
font = run.font
|
|
317
|
+
|
|
318
|
+
if font_size is not None:
|
|
319
|
+
font.size = Pt(font_size)
|
|
320
|
+
if font_name is not None:
|
|
321
|
+
font.name = font_name
|
|
322
|
+
if bold is not None:
|
|
323
|
+
font.bold = bold
|
|
324
|
+
if italic is not None:
|
|
325
|
+
font.italic = italic
|
|
326
|
+
if underline is not None:
|
|
327
|
+
font.underline = underline
|
|
328
|
+
if color is not None:
|
|
329
|
+
r, g, b = color
|
|
330
|
+
font.color.rgb = RGBColor(r, g, b)
|
|
331
|
+
|
|
332
|
+
return result
|
|
333
|
+
|
|
334
|
+
except Exception as e:
|
|
335
|
+
result['success'] = False
|
|
336
|
+
result['error'] = str(e)
|
|
337
|
+
return result
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def add_image(slide, image_path: str, left: float, top: float, width: float = None, height: float = None) -> Any:
|
|
341
|
+
"""
|
|
342
|
+
Add an image to a slide.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
slide: The slide object
|
|
346
|
+
image_path: Path to the image file
|
|
347
|
+
left: Left position in inches
|
|
348
|
+
top: Top position in inches
|
|
349
|
+
width: Width in inches (optional)
|
|
350
|
+
height: Height in inches (optional)
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
The created image shape
|
|
354
|
+
"""
|
|
355
|
+
if width is not None and height is not None:
|
|
356
|
+
return slide.shapes.add_picture(
|
|
357
|
+
image_path, Inches(left), Inches(top), Inches(width), Inches(height)
|
|
358
|
+
)
|
|
359
|
+
elif width is not None:
|
|
360
|
+
return slide.shapes.add_picture(
|
|
361
|
+
image_path, Inches(left), Inches(top), Inches(width)
|
|
362
|
+
)
|
|
363
|
+
elif height is not None:
|
|
364
|
+
return slide.shapes.add_picture(
|
|
365
|
+
image_path, Inches(left), Inches(top), height=Inches(height)
|
|
366
|
+
)
|
|
367
|
+
else:
|
|
368
|
+
return slide.shapes.add_picture(
|
|
369
|
+
image_path, Inches(left), Inches(top)
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def add_table(slide, rows: int, cols: int, left: float, top: float, width: float, height: float) -> Any:
|
|
374
|
+
"""
|
|
375
|
+
Add a table to a slide.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
slide: The slide object
|
|
379
|
+
rows: Number of rows
|
|
380
|
+
cols: Number of columns
|
|
381
|
+
left: Left position in inches
|
|
382
|
+
top: Top position in inches
|
|
383
|
+
width: Width in inches
|
|
384
|
+
height: Height in inches
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
The created table shape
|
|
388
|
+
"""
|
|
389
|
+
return slide.shapes.add_table(
|
|
390
|
+
rows, cols, Inches(left), Inches(top), Inches(width), Inches(height)
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def format_table_cell(cell, font_size: int = None, font_name: str = None,
|
|
395
|
+
bold: bool = None, italic: bool = None,
|
|
396
|
+
color: Tuple[int, int, int] = None, bg_color: Tuple[int, int, int] = None,
|
|
397
|
+
alignment: str = None, vertical_alignment: str = None) -> None:
|
|
398
|
+
"""
|
|
399
|
+
Format a table cell.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
cell: The table cell object
|
|
403
|
+
font_size: Font size in points
|
|
404
|
+
font_name: Font name
|
|
405
|
+
bold: Whether text should be bold
|
|
406
|
+
italic: Whether text should be italic
|
|
407
|
+
color: RGB color tuple (r, g, b)
|
|
408
|
+
bg_color: Background RGB color tuple (r, g, b)
|
|
409
|
+
alignment: Text alignment
|
|
410
|
+
vertical_alignment: Vertical alignment
|
|
411
|
+
"""
|
|
412
|
+
# Format text
|
|
413
|
+
if any([font_size, font_name, bold, italic, color, alignment]):
|
|
414
|
+
format_text_advanced(
|
|
415
|
+
cell.text_frame,
|
|
416
|
+
font_size=font_size,
|
|
417
|
+
font_name=font_name,
|
|
418
|
+
bold=bold,
|
|
419
|
+
italic=italic,
|
|
420
|
+
color=color,
|
|
421
|
+
alignment=alignment
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Set background color
|
|
425
|
+
if bg_color:
|
|
426
|
+
cell.fill.solid()
|
|
427
|
+
cell.fill.fore_color.rgb = RGBColor(*bg_color)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def add_chart(slide, chart_type: str, left: float, top: float, width: float, height: float,
|
|
431
|
+
categories: List[str], series_names: List[str], series_values: List[List[float]]) -> Any:
|
|
432
|
+
"""
|
|
433
|
+
Add a chart to a slide.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
slide: The slide object
|
|
437
|
+
chart_type: Type of chart ('column', 'bar', 'line', 'pie', etc.)
|
|
438
|
+
left: Left position in inches
|
|
439
|
+
top: Top position in inches
|
|
440
|
+
width: Width in inches
|
|
441
|
+
height: Height in inches
|
|
442
|
+
categories: List of category names
|
|
443
|
+
series_names: List of series names
|
|
444
|
+
series_values: List of value lists for each series
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
The created chart object
|
|
448
|
+
"""
|
|
449
|
+
# Map chart type names to enum values
|
|
450
|
+
chart_type_map = {
|
|
451
|
+
'column': XL_CHART_TYPE.COLUMN_CLUSTERED,
|
|
452
|
+
'stacked_column': XL_CHART_TYPE.COLUMN_STACKED,
|
|
453
|
+
'bar': XL_CHART_TYPE.BAR_CLUSTERED,
|
|
454
|
+
'stacked_bar': XL_CHART_TYPE.BAR_STACKED,
|
|
455
|
+
'line': XL_CHART_TYPE.LINE,
|
|
456
|
+
'line_markers': XL_CHART_TYPE.LINE_MARKERS,
|
|
457
|
+
'pie': XL_CHART_TYPE.PIE,
|
|
458
|
+
'doughnut': XL_CHART_TYPE.DOUGHNUT,
|
|
459
|
+
'area': XL_CHART_TYPE.AREA,
|
|
460
|
+
'stacked_area': XL_CHART_TYPE.AREA_STACKED,
|
|
461
|
+
'scatter': XL_CHART_TYPE.XY_SCATTER,
|
|
462
|
+
'radar': XL_CHART_TYPE.RADAR,
|
|
463
|
+
'radar_markers': XL_CHART_TYPE.RADAR_MARKERS
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
xl_chart_type = chart_type_map.get(chart_type.lower(), XL_CHART_TYPE.COLUMN_CLUSTERED)
|
|
467
|
+
|
|
468
|
+
# Create chart data
|
|
469
|
+
chart_data = CategoryChartData()
|
|
470
|
+
chart_data.categories = categories
|
|
471
|
+
|
|
472
|
+
for i, series_name in enumerate(series_names):
|
|
473
|
+
if i < len(series_values):
|
|
474
|
+
chart_data.add_series(series_name, series_values[i])
|
|
475
|
+
|
|
476
|
+
# Add chart to slide
|
|
477
|
+
chart_shape = slide.shapes.add_chart(
|
|
478
|
+
xl_chart_type, Inches(left), Inches(top), Inches(width), Inches(height), chart_data
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
return chart_shape.chart
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def format_chart(chart, has_legend: bool = True, legend_position: str = 'right',
|
|
485
|
+
has_data_labels: bool = False, title: str = None,
|
|
486
|
+
x_axis_title: str = None, y_axis_title: str = None,
|
|
487
|
+
color_scheme: str = None) -> None:
|
|
488
|
+
"""
|
|
489
|
+
Format a chart with various options.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
chart: The chart object
|
|
493
|
+
has_legend: Whether to show legend
|
|
494
|
+
legend_position: Position of legend ('right', 'top', 'bottom', 'left')
|
|
495
|
+
has_data_labels: Whether to show data labels
|
|
496
|
+
title: Chart title
|
|
497
|
+
x_axis_title: X-axis title
|
|
498
|
+
y_axis_title: Y-axis title
|
|
499
|
+
color_scheme: Color scheme to apply
|
|
500
|
+
"""
|
|
501
|
+
try:
|
|
502
|
+
# Set chart title
|
|
503
|
+
if title:
|
|
504
|
+
chart.chart_title.text_frame.text = title
|
|
505
|
+
|
|
506
|
+
# Configure legend
|
|
507
|
+
if has_legend:
|
|
508
|
+
chart.has_legend = True
|
|
509
|
+
# Note: Legend position setting may vary by chart type
|
|
510
|
+
else:
|
|
511
|
+
chart.has_legend = False
|
|
512
|
+
|
|
513
|
+
# Configure data labels
|
|
514
|
+
if has_data_labels:
|
|
515
|
+
for series in chart.series:
|
|
516
|
+
series.has_data_labels = True
|
|
517
|
+
|
|
518
|
+
# Set axis titles if available
|
|
519
|
+
try:
|
|
520
|
+
if x_axis_title and hasattr(chart, 'category_axis'):
|
|
521
|
+
chart.category_axis.axis_title.text_frame.text = x_axis_title
|
|
522
|
+
if y_axis_title and hasattr(chart, 'value_axis'):
|
|
523
|
+
chart.value_axis.axis_title.text_frame.text = y_axis_title
|
|
524
|
+
except:
|
|
525
|
+
pass # Axis titles may not be available for all chart types
|
|
526
|
+
|
|
527
|
+
except Exception:
|
|
528
|
+
pass # Graceful degradation for chart formatting
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def extract_slide_text_content(slide) -> Dict:
|
|
532
|
+
"""
|
|
533
|
+
Extract all text content from a slide including placeholders and text shapes.
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
slide: The slide object to extract text from
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Dictionary containing all text content organized by source type
|
|
540
|
+
"""
|
|
541
|
+
try:
|
|
542
|
+
text_content = {
|
|
543
|
+
"slide_title": "",
|
|
544
|
+
"placeholders": [],
|
|
545
|
+
"text_shapes": [],
|
|
546
|
+
"table_text": [],
|
|
547
|
+
"all_text_combined": ""
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
all_texts = []
|
|
551
|
+
|
|
552
|
+
# Extract title from slide if available
|
|
553
|
+
if hasattr(slide, 'shapes') and hasattr(slide.shapes, 'title') and slide.shapes.title:
|
|
554
|
+
try:
|
|
555
|
+
title_text = slide.shapes.title.text_frame.text.strip()
|
|
556
|
+
if title_text:
|
|
557
|
+
text_content["slide_title"] = title_text
|
|
558
|
+
all_texts.append(title_text)
|
|
559
|
+
except:
|
|
560
|
+
pass
|
|
561
|
+
|
|
562
|
+
# Extract text from all shapes
|
|
563
|
+
for i, shape in enumerate(slide.shapes):
|
|
564
|
+
shape_text_info = {
|
|
565
|
+
"shape_index": i,
|
|
566
|
+
"shape_name": shape.name,
|
|
567
|
+
"shape_type": str(shape.shape_type),
|
|
568
|
+
"text": ""
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
try:
|
|
572
|
+
# Check if shape has text frame
|
|
573
|
+
if hasattr(shape, 'text_frame') and shape.text_frame:
|
|
574
|
+
text = shape.text_frame.text.strip()
|
|
575
|
+
if text:
|
|
576
|
+
shape_text_info["text"] = text
|
|
577
|
+
all_texts.append(text)
|
|
578
|
+
|
|
579
|
+
# Categorize by shape type
|
|
580
|
+
if hasattr(shape, 'placeholder_format'):
|
|
581
|
+
# This is a placeholder
|
|
582
|
+
placeholder_info = shape_text_info.copy()
|
|
583
|
+
placeholder_info["placeholder_type"] = str(shape.placeholder_format.type)
|
|
584
|
+
placeholder_info["placeholder_idx"] = shape.placeholder_format.idx
|
|
585
|
+
text_content["placeholders"].append(placeholder_info)
|
|
586
|
+
else:
|
|
587
|
+
# This is a regular text shape
|
|
588
|
+
text_content["text_shapes"].append(shape_text_info)
|
|
589
|
+
|
|
590
|
+
# Extract text from tables
|
|
591
|
+
elif hasattr(shape, 'table'):
|
|
592
|
+
table_texts = []
|
|
593
|
+
table = shape.table
|
|
594
|
+
for row_idx, row in enumerate(table.rows):
|
|
595
|
+
row_texts = []
|
|
596
|
+
for col_idx, cell in enumerate(row.cells):
|
|
597
|
+
cell_text = cell.text_frame.text.strip()
|
|
598
|
+
if cell_text:
|
|
599
|
+
row_texts.append(cell_text)
|
|
600
|
+
all_texts.append(cell_text)
|
|
601
|
+
if row_texts:
|
|
602
|
+
table_texts.append({
|
|
603
|
+
"row": row_idx,
|
|
604
|
+
"cells": row_texts
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
if table_texts:
|
|
608
|
+
text_content["table_text"].append({
|
|
609
|
+
"shape_index": i,
|
|
610
|
+
"shape_name": shape.name,
|
|
611
|
+
"table_content": table_texts
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
except Exception as e:
|
|
615
|
+
# Skip shapes that can't be processed
|
|
616
|
+
continue
|
|
617
|
+
|
|
618
|
+
# Combine all text
|
|
619
|
+
text_content["all_text_combined"] = "\n".join(all_texts)
|
|
620
|
+
|
|
621
|
+
return {
|
|
622
|
+
"success": True,
|
|
623
|
+
"text_content": text_content,
|
|
624
|
+
"total_text_shapes": len(text_content["placeholders"]) + len(text_content["text_shapes"]),
|
|
625
|
+
"has_title": bool(text_content["slide_title"]),
|
|
626
|
+
"has_tables": len(text_content["table_text"]) > 0
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
except Exception as e:
|
|
630
|
+
return {
|
|
631
|
+
"success": False,
|
|
632
|
+
"error": f"Failed to extract text content: {str(e)}",
|
|
633
|
+
"text_content": None
|
|
579
634
|
}
|