mcpcn-office-powerpoint-mcp-server 2.1.0__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.
tools/template_tools.py CHANGED
@@ -1,521 +1,521 @@
1
- """
2
- Enhanced template-based slide creation tools for PowerPoint MCP Server.
3
- Handles template application, template management, automated slide generation,
4
- and advanced features like dynamic sizing, auto-wrapping, and visual effects.
5
- """
6
- from typing import Dict, List, Optional, Any
7
- from mcp.server.fastmcp import FastMCP
8
- import utils.template_utils as template_utils
9
-
10
-
11
- def register_template_tools(app: FastMCP, presentations: Dict, get_current_presentation_id):
12
- """Register template-based tools with the FastMCP app"""
13
-
14
- @app.tool()
15
- def list_slide_templates() -> Dict:
16
- """List all available slide layout templates."""
17
- try:
18
- available_templates = template_utils.get_available_templates()
19
- usage_examples = template_utils.get_template_usage_examples()
20
-
21
- return {
22
- "available_templates": available_templates,
23
- "total_templates": len(available_templates),
24
- "usage_examples": usage_examples,
25
- "message": "Use apply_slide_template to apply templates to slides"
26
- }
27
- except Exception as e:
28
- return {
29
- "error": f"Failed to list templates: {str(e)}"
30
- }
31
-
32
- @app.tool()
33
- def apply_slide_template(
34
- slide_index: int,
35
- template_id: str,
36
- color_scheme: str = "modern_blue",
37
- content_mapping: Optional[Dict[str, str]] = None,
38
- image_paths: Optional[Dict[str, str]] = None,
39
- presentation_id: Optional[str] = None
40
- ) -> Dict:
41
- """
42
- Apply a structured layout template to an existing slide.
43
- This modifies slide layout and content structure using predefined templates.
44
-
45
- Args:
46
- slide_index: Index of the slide to apply template to
47
- template_id: ID of the template to apply (e.g., 'title_slide', 'text_with_image')
48
- color_scheme: Color scheme to use ('modern_blue', 'corporate_gray', 'elegant_green', 'warm_red')
49
- content_mapping: Dictionary mapping element roles to custom content
50
- image_paths: Dictionary mapping image element roles to file paths
51
- presentation_id: Presentation ID (uses current if None)
52
- """
53
- pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
54
-
55
- if pres_id is None or pres_id not in presentations:
56
- return {
57
- "error": "No presentation is currently loaded or the specified ID is invalid"
58
- }
59
-
60
- pres = presentations[pres_id]
61
-
62
- if slide_index < 0 or slide_index >= len(pres.slides):
63
- return {
64
- "error": f"Invalid slide index: {slide_index}. Available slides: 0-{len(pres.slides) - 1}"
65
- }
66
-
67
- slide = pres.slides[slide_index]
68
-
69
- try:
70
- result = template_utils.apply_slide_template(
71
- slide, template_id, color_scheme,
72
- content_mapping or {}, image_paths or {}
73
- )
74
-
75
- if result['success']:
76
- return {
77
- "message": f"Applied template '{template_id}' to slide {slide_index}",
78
- "slide_index": slide_index,
79
- "template_applied": result
80
- }
81
- else:
82
- return {
83
- "error": f"Failed to apply template: {result.get('error', 'Unknown error')}"
84
- }
85
-
86
- except Exception as e:
87
- return {
88
- "error": f"Failed to apply template: {str(e)}"
89
- }
90
-
91
- @app.tool()
92
- def create_slide_from_template(
93
- template_id: str,
94
- color_scheme: str = "modern_blue",
95
- content_mapping: Optional[Dict[str, str]] = None,
96
- image_paths: Optional[Dict[str, str]] = None,
97
- layout_index: int = 1,
98
- presentation_id: Optional[str] = None
99
- ) -> Dict:
100
- """
101
- Create a new slide using a layout template.
102
-
103
- Args:
104
- template_id: ID of the template to use (e.g., 'title_slide', 'text_with_image')
105
- color_scheme: Color scheme to use ('modern_blue', 'corporate_gray', 'elegant_green', 'warm_red')
106
- content_mapping: Dictionary mapping element roles to custom content
107
- image_paths: Dictionary mapping image element roles to file paths
108
- layout_index: PowerPoint layout index to use as base (default: 1)
109
- presentation_id: Presentation ID (uses current if None)
110
- """
111
- pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
112
-
113
- if pres_id is None or pres_id not in presentations:
114
- return {
115
- "error": "No presentation is currently loaded or the specified ID is invalid"
116
- }
117
-
118
- pres = presentations[pres_id]
119
-
120
- # Validate layout index
121
- if layout_index < 0 or layout_index >= len(pres.slide_layouts):
122
- return {
123
- "error": f"Invalid layout index: {layout_index}. Available layouts: 0-{len(pres.slide_layouts) - 1}"
124
- }
125
-
126
- try:
127
- # Add new slide
128
- layout = pres.slide_layouts[layout_index]
129
- slide = pres.slides.add_slide(layout)
130
- slide_index = len(pres.slides) - 1
131
-
132
- # Apply template
133
- result = template_utils.apply_slide_template(
134
- slide, template_id, color_scheme,
135
- content_mapping or {}, image_paths or {}
136
- )
137
-
138
- if result['success']:
139
- return {
140
- "message": f"Created slide {slide_index} using template '{template_id}'",
141
- "slide_index": slide_index,
142
- "template_applied": result
143
- }
144
- else:
145
- return {
146
- "error": f"Failed to apply template to new slide: {result.get('error', 'Unknown error')}"
147
- }
148
-
149
- except Exception as e:
150
- return {
151
- "error": f"Failed to create slide from template: {str(e)}"
152
- }
153
-
154
- @app.tool()
155
- def create_presentation_from_templates(
156
- template_sequence: List[Dict[str, Any]],
157
- color_scheme: str = "modern_blue",
158
- presentation_title: Optional[str] = None,
159
- presentation_id: Optional[str] = None
160
- ) -> Dict:
161
- """
162
- Create a complete presentation from a sequence of templates.
163
-
164
- Args:
165
- template_sequence: List of template configurations, each containing:
166
- - template_id: Template to use
167
- - content: Content mapping for the template
168
- - images: Image path mapping for the template
169
- color_scheme: Color scheme to apply to all slides
170
- presentation_title: Optional title for the presentation
171
- presentation_id: Presentation ID (uses current if None)
172
-
173
- Example template_sequence:
174
- [
175
- {
176
- "template_id": "title_slide",
177
- "content": {
178
- "title": "My Presentation",
179
- "subtitle": "Annual Report 2024",
180
- "author": "John Doe"
181
- }
182
- },
183
- {
184
- "template_id": "text_with_image",
185
- "content": {
186
- "title": "Key Results",
187
- "content": "• Achievement 1\\n• Achievement 2"
188
- },
189
- "images": {
190
- "supporting": "/path/to/image.jpg"
191
- }
192
- }
193
- ]
194
- """
195
- pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
196
-
197
- if pres_id is None or pres_id not in presentations:
198
- return {
199
- "error": "No presentation is currently loaded or the specified ID is invalid"
200
- }
201
-
202
- pres = presentations[pres_id]
203
-
204
- if not template_sequence:
205
- return {
206
- "error": "Template sequence cannot be empty"
207
- }
208
-
209
- try:
210
- # Set presentation title if provided
211
- if presentation_title:
212
- pres.core_properties.title = presentation_title
213
-
214
- # Create slides from template sequence
215
- result = template_utils.create_presentation_from_template_sequence(
216
- pres, template_sequence, color_scheme
217
- )
218
-
219
- if result['success']:
220
- return {
221
- "message": f"Created presentation with {result['total_slides']} slides",
222
- "presentation_id": pres_id,
223
- "creation_result": result,
224
- "total_slides": len(pres.slides)
225
- }
226
- else:
227
- return {
228
- "warning": "Presentation created with some errors",
229
- "presentation_id": pres_id,
230
- "creation_result": result,
231
- "total_slides": len(pres.slides)
232
- }
233
-
234
- except Exception as e:
235
- return {
236
- "error": f"Failed to create presentation from templates: {str(e)}"
237
- }
238
-
239
- @app.tool()
240
- def get_template_info(template_id: str) -> Dict:
241
- """
242
- Get detailed information about a specific template.
243
-
244
- Args:
245
- template_id: ID of the template to get information about
246
- """
247
- try:
248
- templates_data = template_utils.load_slide_templates()
249
-
250
- if template_id not in templates_data.get('templates', {}):
251
- available_templates = list(templates_data.get('templates', {}).keys())
252
- return {
253
- "error": f"Template '{template_id}' not found",
254
- "available_templates": available_templates
255
- }
256
-
257
- template = templates_data['templates'][template_id]
258
-
259
- # Extract element information
260
- elements_info = []
261
- for element in template.get('elements', []):
262
- element_info = {
263
- "type": element.get('type'),
264
- "role": element.get('role'),
265
- "position": element.get('position'),
266
- "placeholder_text": element.get('placeholder_text', ''),
267
- "styling_options": list(element.get('styling', {}).keys())
268
- }
269
- elements_info.append(element_info)
270
-
271
- return {
272
- "template_id": template_id,
273
- "name": template.get('name'),
274
- "description": template.get('description'),
275
- "layout_type": template.get('layout_type'),
276
- "elements": elements_info,
277
- "element_count": len(elements_info),
278
- "has_background": 'background' in template,
279
- "background_type": template.get('background', {}).get('type'),
280
- "color_schemes": list(templates_data.get('color_schemes', {}).keys()),
281
- "usage_tip": f"Use create_slide_from_template with template_id='{template_id}' to create a slide with this layout"
282
- }
283
-
284
- except Exception as e:
285
- return {
286
- "error": f"Failed to get template info: {str(e)}"
287
- }
288
-
289
- @app.tool()
290
- def auto_generate_presentation(
291
- topic: str,
292
- slide_count: int = 5,
293
- presentation_type: str = "business",
294
- color_scheme: str = "modern_blue",
295
- include_charts: bool = True,
296
- include_images: bool = False,
297
- presentation_id: Optional[str] = None
298
- ) -> Dict:
299
- """
300
- Automatically generate a presentation based on topic and preferences.
301
-
302
- Args:
303
- topic: Main topic/theme for the presentation
304
- slide_count: Number of slides to generate (3-20)
305
- presentation_type: Type of presentation ('business', 'academic', 'creative')
306
- color_scheme: Color scheme to use
307
- include_charts: Whether to include chart slides
308
- include_images: Whether to include image placeholders
309
- presentation_id: Presentation ID (uses current if None)
310
- """
311
- pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
312
-
313
- if pres_id is None or pres_id not in presentations:
314
- return {
315
- "error": "No presentation is currently loaded or the specified ID is invalid"
316
- }
317
-
318
- if slide_count < 3 or slide_count > 20:
319
- return {
320
- "error": "Slide count must be between 3 and 20"
321
- }
322
-
323
- try:
324
- # Define presentation structures based on type
325
- if presentation_type == "business":
326
- base_templates = [
327
- ("title_slide", {"title": f"{topic}", "subtitle": "Executive Presentation", "author": "Business Team"}),
328
- ("agenda_slide", {"agenda_items": "1. Executive Summary\n\n2. Current Situation\n\n3. Analysis & Insights\n\n4. Recommendations\n\n5. Next Steps"}),
329
- ("key_metrics_dashboard", {"title": "Key Performance Indicators"}),
330
- ("text_with_image", {"title": "Current Situation", "content": f"Overview of {topic}:\n• Current status\n• Key challenges\n• Market position"}),
331
- ("two_column_text", {"title": "Analysis", "content_left": "Strengths:\n• Advantage 1\n• Advantage 2\n• Advantage 3", "content_right": "Opportunities:\n• Opportunity 1\n• Opportunity 2\n• Opportunity 3"}),
332
- ]
333
- if include_charts:
334
- base_templates.append(("chart_comparison", {"title": "Performance Comparison"}))
335
- base_templates.append(("thank_you_slide", {"contact": "Thank you for your attention\nQuestions & Discussion"}))
336
-
337
- elif presentation_type == "academic":
338
- base_templates = [
339
- ("title_slide", {"title": f"Research on {topic}", "subtitle": "Academic Study", "author": "Research Team"}),
340
- ("agenda_slide", {"agenda_items": "1. Introduction\n\n2. Literature Review\n\n3. Methodology\n\n4. Results\n\n5. Conclusions"}),
341
- ("text_with_image", {"title": "Introduction", "content": f"Research focus on {topic}:\n• Background\n• Problem statement\n• Research questions"}),
342
- ("two_column_text", {"title": "Methodology", "content_left": "Approach:\n• Method 1\n• Method 2\n• Method 3", "content_right": "Data Sources:\n• Source 1\n• Source 2\n• Source 3"}),
343
- ("data_table_slide", {"title": "Results Summary"}),
344
- ]
345
- if include_charts:
346
- base_templates.append(("chart_comparison", {"title": "Data Analysis"}))
347
- base_templates.append(("thank_you_slide", {"contact": "Questions & Discussion\nContact: research@university.edu"}))
348
-
349
- else: # creative
350
- base_templates = [
351
- ("title_slide", {"title": f"Creative Vision: {topic}", "subtitle": "Innovative Concepts", "author": "Creative Team"}),
352
- ("full_image_slide", {"overlay_title": f"Exploring {topic}", "overlay_subtitle": "Creative possibilities"}),
353
- ("three_column_layout", {"title": "Creative Concepts"}),
354
- ("quote_testimonial", {"quote_text": f"Innovation in {topic} requires thinking beyond conventional boundaries", "attribution": "— Creative Director"}),
355
- ("process_flow", {"title": "Creative Process"}),
356
- ]
357
- if include_charts:
358
- base_templates.append(("key_metrics_dashboard", {"title": "Impact Metrics"}))
359
- base_templates.append(("thank_you_slide", {"contact": "Let's create something amazing together\ncreative@studio.com"}))
360
-
361
- # Adjust templates to match requested slide count
362
- template_sequence = []
363
- templates_to_use = base_templates[:slide_count]
364
-
365
- # If we need more slides, add content slides
366
- while len(templates_to_use) < slide_count:
367
- if include_images:
368
- templates_to_use.insert(-1, ("text_with_image", {"title": f"{topic} - Additional Topic", "content": "• Key point\n• Supporting detail\n• Additional insight"}))
369
- else:
370
- templates_to_use.insert(-1, ("two_column_text", {"title": f"{topic} - Analysis", "content_left": "Key Points:\n• Point 1\n• Point 2", "content_right": "Details:\n• Detail 1\n• Detail 2"}))
371
-
372
- # Convert to proper template sequence format
373
- for i, (template_id, content) in enumerate(templates_to_use):
374
- template_config = {
375
- "template_id": template_id,
376
- "content": content
377
- }
378
- template_sequence.append(template_config)
379
-
380
- # Create the presentation
381
- result = template_utils.create_presentation_from_template_sequence(
382
- presentations[pres_id], template_sequence, color_scheme
383
- )
384
-
385
- return {
386
- "message": f"Auto-generated {slide_count}-slide presentation on '{topic}'",
387
- "topic": topic,
388
- "presentation_type": presentation_type,
389
- "color_scheme": color_scheme,
390
- "slide_count": slide_count,
391
- "generation_result": result,
392
- "templates_used": [t[0] for t in templates_to_use]
393
- }
394
-
395
- except Exception as e:
396
- return {
397
- "error": f"Failed to auto-generate presentation: {str(e)}"
398
- }
399
-
400
- # Text optimization tools
401
-
402
-
403
- @app.tool()
404
- def optimize_slide_text(
405
- slide_index: int,
406
- auto_resize: bool = True,
407
- auto_wrap: bool = True,
408
- optimize_spacing: bool = True,
409
- min_font_size: int = 8,
410
- max_font_size: int = 36,
411
- presentation_id: Optional[str] = None
412
- ) -> Dict:
413
- """
414
- Optimize text elements on a slide for better readability and fit.
415
-
416
- Args:
417
- slide_index: Index of the slide to optimize
418
- auto_resize: Whether to automatically resize fonts to fit containers
419
- auto_wrap: Whether to apply intelligent text wrapping
420
- optimize_spacing: Whether to optimize line spacing
421
- min_font_size: Minimum allowed font size
422
- max_font_size: Maximum allowed font size
423
- presentation_id: Presentation ID (uses current if None)
424
- """
425
- pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
426
-
427
- if pres_id is None or pres_id not in presentations:
428
- return {
429
- "error": "No presentation is currently loaded or the specified ID is invalid"
430
- }
431
-
432
- pres = presentations[pres_id]
433
-
434
- if slide_index < 0 or slide_index >= len(pres.slides):
435
- return {
436
- "error": f"Invalid slide index: {slide_index}. Available slides: 0-{len(pres.slides) - 1}"
437
- }
438
-
439
- slide = pres.slides[slide_index]
440
-
441
- try:
442
- optimizations_applied = []
443
- manager = template_utils.get_enhanced_template_manager()
444
-
445
- # Analyze each text shape on the slide
446
- for i, shape in enumerate(slide.shapes):
447
- if hasattr(shape, 'text_frame') and shape.text_frame.text:
448
- text = shape.text_frame.text
449
-
450
- # Calculate container dimensions
451
- container_width = shape.width.inches
452
- container_height = shape.height.inches
453
-
454
- shape_optimizations = []
455
-
456
- # Apply auto-resize if enabled
457
- if auto_resize:
458
- optimal_size = template_utils.calculate_dynamic_font_size(
459
- text, container_width, container_height
460
- )
461
- optimal_size = max(min_font_size, min(max_font_size, optimal_size))
462
-
463
- # Apply the calculated font size
464
- for paragraph in shape.text_frame.paragraphs:
465
- for run in paragraph.runs:
466
- run.font.size = template_utils.Pt(optimal_size)
467
-
468
- shape_optimizations.append(f"Font resized to {optimal_size}pt")
469
-
470
- # Apply auto-wrap if enabled
471
- if auto_wrap:
472
- current_font_size = 14 # Default assumption
473
- if shape.text_frame.paragraphs and shape.text_frame.paragraphs[0].runs:
474
- if shape.text_frame.paragraphs[0].runs[0].font.size:
475
- current_font_size = shape.text_frame.paragraphs[0].runs[0].font.size.pt
476
-
477
- wrapped_text = template_utils.wrap_text_automatically(
478
- text, container_width, current_font_size
479
- )
480
-
481
- if wrapped_text != text:
482
- shape.text_frame.text = wrapped_text
483
- shape_optimizations.append("Text wrapped automatically")
484
-
485
- # Optimize spacing if enabled
486
- if optimize_spacing:
487
- text_length = len(text)
488
- if text_length > 300:
489
- line_spacing = 1.4
490
- elif text_length > 150:
491
- line_spacing = 1.3
492
- else:
493
- line_spacing = 1.2
494
-
495
- for paragraph in shape.text_frame.paragraphs:
496
- paragraph.line_spacing = line_spacing
497
-
498
- shape_optimizations.append(f"Line spacing set to {line_spacing}")
499
-
500
- if shape_optimizations:
501
- optimizations_applied.append({
502
- "shape_index": i,
503
- "optimizations": shape_optimizations
504
- })
505
-
506
- return {
507
- "message": f"Optimized {len(optimizations_applied)} text elements on slide {slide_index}",
508
- "slide_index": slide_index,
509
- "optimizations_applied": optimizations_applied,
510
- "settings": {
511
- "auto_resize": auto_resize,
512
- "auto_wrap": auto_wrap,
513
- "optimize_spacing": optimize_spacing,
514
- "font_size_range": f"{min_font_size}-{max_font_size}pt"
515
- }
516
- }
517
-
518
- except Exception as e:
519
- return {
520
- "error": f"Failed to optimize slide text: {str(e)}"
1
+ """
2
+ Enhanced template-based slide creation tools for PowerPoint MCP Server.
3
+ Handles template application, template management, automated slide generation,
4
+ and advanced features like dynamic sizing, auto-wrapping, and visual effects.
5
+ """
6
+ from typing import Dict, List, Optional, Any
7
+ from mcp.server.fastmcp import FastMCP
8
+ import utils.template_utils as template_utils
9
+
10
+
11
+ def register_template_tools(app: FastMCP, presentations: Dict, get_current_presentation_id):
12
+ """Register template-based tools with the FastMCP app"""
13
+
14
+ @app.tool()
15
+ def list_slide_templates() -> Dict:
16
+ """List all available slide layout templates."""
17
+ try:
18
+ available_templates = template_utils.get_available_templates()
19
+ usage_examples = template_utils.get_template_usage_examples()
20
+
21
+ return {
22
+ "available_templates": available_templates,
23
+ "total_templates": len(available_templates),
24
+ "usage_examples": usage_examples,
25
+ "message": "Use apply_slide_template to apply templates to slides"
26
+ }
27
+ except Exception as e:
28
+ return {
29
+ "error": f"Failed to list templates: {str(e)}"
30
+ }
31
+
32
+ @app.tool()
33
+ def apply_slide_template(
34
+ slide_index: int,
35
+ template_id: str,
36
+ color_scheme: str = "modern_blue",
37
+ content_mapping: Optional[Dict[str, str]] = None,
38
+ image_paths: Optional[Dict[str, str]] = None,
39
+ presentation_id: Optional[str] = None
40
+ ) -> Dict:
41
+ """
42
+ Apply a structured layout template to an existing slide.
43
+ This modifies slide layout and content structure using predefined templates.
44
+
45
+ Args:
46
+ slide_index: Index of the slide to apply template to
47
+ template_id: ID of the template to apply (e.g., 'title_slide', 'text_with_image')
48
+ color_scheme: Color scheme to use ('modern_blue', 'corporate_gray', 'elegant_green', 'warm_red')
49
+ content_mapping: Dictionary mapping element roles to custom content
50
+ image_paths: Dictionary mapping image element roles to file paths
51
+ presentation_id: Presentation ID (uses current if None)
52
+ """
53
+ pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
54
+
55
+ if pres_id is None or pres_id not in presentations:
56
+ return {
57
+ "error": "No presentation is currently loaded or the specified ID is invalid"
58
+ }
59
+
60
+ pres = presentations[pres_id]
61
+
62
+ if slide_index < 0 or slide_index >= len(pres.slides):
63
+ return {
64
+ "error": f"Invalid slide index: {slide_index}. Available slides: 0-{len(pres.slides) - 1}"
65
+ }
66
+
67
+ slide = pres.slides[slide_index]
68
+
69
+ try:
70
+ result = template_utils.apply_slide_template(
71
+ slide, template_id, color_scheme,
72
+ content_mapping or {}, image_paths or {}
73
+ )
74
+
75
+ if result['success']:
76
+ return {
77
+ "message": f"Applied template '{template_id}' to slide {slide_index}",
78
+ "slide_index": slide_index,
79
+ "template_applied": result
80
+ }
81
+ else:
82
+ return {
83
+ "error": f"Failed to apply template: {result.get('error', 'Unknown error')}"
84
+ }
85
+
86
+ except Exception as e:
87
+ return {
88
+ "error": f"Failed to apply template: {str(e)}"
89
+ }
90
+
91
+ @app.tool()
92
+ def create_slide_from_template(
93
+ template_id: str,
94
+ color_scheme: str = "modern_blue",
95
+ content_mapping: Optional[Dict[str, str]] = None,
96
+ image_paths: Optional[Dict[str, str]] = None,
97
+ layout_index: int = 1,
98
+ presentation_id: Optional[str] = None
99
+ ) -> Dict:
100
+ """
101
+ Create a new slide using a layout template.
102
+
103
+ Args:
104
+ template_id: ID of the template to use (e.g., 'title_slide', 'text_with_image')
105
+ color_scheme: Color scheme to use ('modern_blue', 'corporate_gray', 'elegant_green', 'warm_red')
106
+ content_mapping: Dictionary mapping element roles to custom content
107
+ image_paths: Dictionary mapping image element roles to file paths
108
+ layout_index: PowerPoint layout index to use as base (default: 1)
109
+ presentation_id: Presentation ID (uses current if None)
110
+ """
111
+ pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
112
+
113
+ if pres_id is None or pres_id not in presentations:
114
+ return {
115
+ "error": "No presentation is currently loaded or the specified ID is invalid"
116
+ }
117
+
118
+ pres = presentations[pres_id]
119
+
120
+ # Validate layout index
121
+ if layout_index < 0 or layout_index >= len(pres.slide_layouts):
122
+ return {
123
+ "error": f"Invalid layout index: {layout_index}. Available layouts: 0-{len(pres.slide_layouts) - 1}"
124
+ }
125
+
126
+ try:
127
+ # Add new slide
128
+ layout = pres.slide_layouts[layout_index]
129
+ slide = pres.slides.add_slide(layout)
130
+ slide_index = len(pres.slides) - 1
131
+
132
+ # Apply template
133
+ result = template_utils.apply_slide_template(
134
+ slide, template_id, color_scheme,
135
+ content_mapping or {}, image_paths or {}
136
+ )
137
+
138
+ if result['success']:
139
+ return {
140
+ "message": f"Created slide {slide_index} using template '{template_id}'",
141
+ "slide_index": slide_index,
142
+ "template_applied": result
143
+ }
144
+ else:
145
+ return {
146
+ "error": f"Failed to apply template to new slide: {result.get('error', 'Unknown error')}"
147
+ }
148
+
149
+ except Exception as e:
150
+ return {
151
+ "error": f"Failed to create slide from template: {str(e)}"
152
+ }
153
+
154
+ @app.tool()
155
+ def create_presentation_from_templates(
156
+ template_sequence: List[Dict[str, Any]],
157
+ color_scheme: str = "modern_blue",
158
+ presentation_title: Optional[str] = None,
159
+ presentation_id: Optional[str] = None
160
+ ) -> Dict:
161
+ """
162
+ Create a complete presentation from a sequence of templates.
163
+
164
+ Args:
165
+ template_sequence: List of template configurations, each containing:
166
+ - template_id: Template to use
167
+ - content: Content mapping for the template
168
+ - images: Image path mapping for the template
169
+ color_scheme: Color scheme to apply to all slides
170
+ presentation_title: Optional title for the presentation
171
+ presentation_id: Presentation ID (uses current if None)
172
+
173
+ Example template_sequence:
174
+ [
175
+ {
176
+ "template_id": "title_slide",
177
+ "content": {
178
+ "title": "My Presentation",
179
+ "subtitle": "Annual Report 2024",
180
+ "author": "John Doe"
181
+ }
182
+ },
183
+ {
184
+ "template_id": "text_with_image",
185
+ "content": {
186
+ "title": "Key Results",
187
+ "content": "• Achievement 1\\n• Achievement 2"
188
+ },
189
+ "images": {
190
+ "supporting": "/path/to/image.jpg"
191
+ }
192
+ }
193
+ ]
194
+ """
195
+ pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
196
+
197
+ if pres_id is None or pres_id not in presentations:
198
+ return {
199
+ "error": "No presentation is currently loaded or the specified ID is invalid"
200
+ }
201
+
202
+ pres = presentations[pres_id]
203
+
204
+ if not template_sequence:
205
+ return {
206
+ "error": "Template sequence cannot be empty"
207
+ }
208
+
209
+ try:
210
+ # Set presentation title if provided
211
+ if presentation_title:
212
+ pres.core_properties.title = presentation_title
213
+
214
+ # Create slides from template sequence
215
+ result = template_utils.create_presentation_from_template_sequence(
216
+ pres, template_sequence, color_scheme
217
+ )
218
+
219
+ if result['success']:
220
+ return {
221
+ "message": f"Created presentation with {result['total_slides']} slides",
222
+ "presentation_id": pres_id,
223
+ "creation_result": result,
224
+ "total_slides": len(pres.slides)
225
+ }
226
+ else:
227
+ return {
228
+ "warning": "Presentation created with some errors",
229
+ "presentation_id": pres_id,
230
+ "creation_result": result,
231
+ "total_slides": len(pres.slides)
232
+ }
233
+
234
+ except Exception as e:
235
+ return {
236
+ "error": f"Failed to create presentation from templates: {str(e)}"
237
+ }
238
+
239
+ @app.tool()
240
+ def get_template_info(template_id: str) -> Dict:
241
+ """
242
+ Get detailed information about a specific template.
243
+
244
+ Args:
245
+ template_id: ID of the template to get information about
246
+ """
247
+ try:
248
+ templates_data = template_utils.load_slide_templates()
249
+
250
+ if template_id not in templates_data.get('templates', {}):
251
+ available_templates = list(templates_data.get('templates', {}).keys())
252
+ return {
253
+ "error": f"Template '{template_id}' not found",
254
+ "available_templates": available_templates
255
+ }
256
+
257
+ template = templates_data['templates'][template_id]
258
+
259
+ # Extract element information
260
+ elements_info = []
261
+ for element in template.get('elements', []):
262
+ element_info = {
263
+ "type": element.get('type'),
264
+ "role": element.get('role'),
265
+ "position": element.get('position'),
266
+ "placeholder_text": element.get('placeholder_text', ''),
267
+ "styling_options": list(element.get('styling', {}).keys())
268
+ }
269
+ elements_info.append(element_info)
270
+
271
+ return {
272
+ "template_id": template_id,
273
+ "name": template.get('name'),
274
+ "description": template.get('description'),
275
+ "layout_type": template.get('layout_type'),
276
+ "elements": elements_info,
277
+ "element_count": len(elements_info),
278
+ "has_background": 'background' in template,
279
+ "background_type": template.get('background', {}).get('type'),
280
+ "color_schemes": list(templates_data.get('color_schemes', {}).keys()),
281
+ "usage_tip": f"Use create_slide_from_template with template_id='{template_id}' to create a slide with this layout"
282
+ }
283
+
284
+ except Exception as e:
285
+ return {
286
+ "error": f"Failed to get template info: {str(e)}"
287
+ }
288
+
289
+ @app.tool()
290
+ def auto_generate_presentation(
291
+ topic: str,
292
+ slide_count: int = 5,
293
+ presentation_type: str = "business",
294
+ color_scheme: str = "modern_blue",
295
+ include_charts: bool = True,
296
+ include_images: bool = False,
297
+ presentation_id: Optional[str] = None
298
+ ) -> Dict:
299
+ """
300
+ Automatically generate a presentation based on topic and preferences.
301
+
302
+ Args:
303
+ topic: Main topic/theme for the presentation
304
+ slide_count: Number of slides to generate (3-20)
305
+ presentation_type: Type of presentation ('business', 'academic', 'creative')
306
+ color_scheme: Color scheme to use
307
+ include_charts: Whether to include chart slides
308
+ include_images: Whether to include image placeholders
309
+ presentation_id: Presentation ID (uses current if None)
310
+ """
311
+ pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
312
+
313
+ if pres_id is None or pres_id not in presentations:
314
+ return {
315
+ "error": "No presentation is currently loaded or the specified ID is invalid"
316
+ }
317
+
318
+ if slide_count < 3 or slide_count > 20:
319
+ return {
320
+ "error": "Slide count must be between 3 and 20"
321
+ }
322
+
323
+ try:
324
+ # Define presentation structures based on type
325
+ if presentation_type == "business":
326
+ base_templates = [
327
+ ("title_slide", {"title": f"{topic}", "subtitle": "Executive Presentation", "author": "Business Team"}),
328
+ ("agenda_slide", {"agenda_items": "1. Executive Summary\n\n2. Current Situation\n\n3. Analysis & Insights\n\n4. Recommendations\n\n5. Next Steps"}),
329
+ ("key_metrics_dashboard", {"title": "Key Performance Indicators"}),
330
+ ("text_with_image", {"title": "Current Situation", "content": f"Overview of {topic}:\n• Current status\n• Key challenges\n• Market position"}),
331
+ ("two_column_text", {"title": "Analysis", "content_left": "Strengths:\n• Advantage 1\n• Advantage 2\n• Advantage 3", "content_right": "Opportunities:\n• Opportunity 1\n• Opportunity 2\n• Opportunity 3"}),
332
+ ]
333
+ if include_charts:
334
+ base_templates.append(("chart_comparison", {"title": "Performance Comparison"}))
335
+ base_templates.append(("thank_you_slide", {"contact": "Thank you for your attention\nQuestions & Discussion"}))
336
+
337
+ elif presentation_type == "academic":
338
+ base_templates = [
339
+ ("title_slide", {"title": f"Research on {topic}", "subtitle": "Academic Study", "author": "Research Team"}),
340
+ ("agenda_slide", {"agenda_items": "1. Introduction\n\n2. Literature Review\n\n3. Methodology\n\n4. Results\n\n5. Conclusions"}),
341
+ ("text_with_image", {"title": "Introduction", "content": f"Research focus on {topic}:\n• Background\n• Problem statement\n• Research questions"}),
342
+ ("two_column_text", {"title": "Methodology", "content_left": "Approach:\n• Method 1\n• Method 2\n• Method 3", "content_right": "Data Sources:\n• Source 1\n• Source 2\n• Source 3"}),
343
+ ("data_table_slide", {"title": "Results Summary"}),
344
+ ]
345
+ if include_charts:
346
+ base_templates.append(("chart_comparison", {"title": "Data Analysis"}))
347
+ base_templates.append(("thank_you_slide", {"contact": "Questions & Discussion\nContact: research@university.edu"}))
348
+
349
+ else: # creative
350
+ base_templates = [
351
+ ("title_slide", {"title": f"Creative Vision: {topic}", "subtitle": "Innovative Concepts", "author": "Creative Team"}),
352
+ ("full_image_slide", {"overlay_title": f"Exploring {topic}", "overlay_subtitle": "Creative possibilities"}),
353
+ ("three_column_layout", {"title": "Creative Concepts"}),
354
+ ("quote_testimonial", {"quote_text": f"Innovation in {topic} requires thinking beyond conventional boundaries", "attribution": "— Creative Director"}),
355
+ ("process_flow", {"title": "Creative Process"}),
356
+ ]
357
+ if include_charts:
358
+ base_templates.append(("key_metrics_dashboard", {"title": "Impact Metrics"}))
359
+ base_templates.append(("thank_you_slide", {"contact": "Let's create something amazing together\ncreative@studio.com"}))
360
+
361
+ # Adjust templates to match requested slide count
362
+ template_sequence = []
363
+ templates_to_use = base_templates[:slide_count]
364
+
365
+ # If we need more slides, add content slides
366
+ while len(templates_to_use) < slide_count:
367
+ if include_images:
368
+ templates_to_use.insert(-1, ("text_with_image", {"title": f"{topic} - Additional Topic", "content": "• Key point\n• Supporting detail\n• Additional insight"}))
369
+ else:
370
+ templates_to_use.insert(-1, ("two_column_text", {"title": f"{topic} - Analysis", "content_left": "Key Points:\n• Point 1\n• Point 2", "content_right": "Details:\n• Detail 1\n• Detail 2"}))
371
+
372
+ # Convert to proper template sequence format
373
+ for i, (template_id, content) in enumerate(templates_to_use):
374
+ template_config = {
375
+ "template_id": template_id,
376
+ "content": content
377
+ }
378
+ template_sequence.append(template_config)
379
+
380
+ # Create the presentation
381
+ result = template_utils.create_presentation_from_template_sequence(
382
+ presentations[pres_id], template_sequence, color_scheme
383
+ )
384
+
385
+ return {
386
+ "message": f"Auto-generated {slide_count}-slide presentation on '{topic}'",
387
+ "topic": topic,
388
+ "presentation_type": presentation_type,
389
+ "color_scheme": color_scheme,
390
+ "slide_count": slide_count,
391
+ "generation_result": result,
392
+ "templates_used": [t[0] for t in templates_to_use]
393
+ }
394
+
395
+ except Exception as e:
396
+ return {
397
+ "error": f"Failed to auto-generate presentation: {str(e)}"
398
+ }
399
+
400
+ # Text optimization tools
401
+
402
+
403
+ @app.tool()
404
+ def optimize_slide_text(
405
+ slide_index: int,
406
+ auto_resize: bool = True,
407
+ auto_wrap: bool = True,
408
+ optimize_spacing: bool = True,
409
+ min_font_size: int = 8,
410
+ max_font_size: int = 36,
411
+ presentation_id: Optional[str] = None
412
+ ) -> Dict:
413
+ """
414
+ Optimize text elements on a slide for better readability and fit.
415
+
416
+ Args:
417
+ slide_index: Index of the slide to optimize
418
+ auto_resize: Whether to automatically resize fonts to fit containers
419
+ auto_wrap: Whether to apply intelligent text wrapping
420
+ optimize_spacing: Whether to optimize line spacing
421
+ min_font_size: Minimum allowed font size
422
+ max_font_size: Maximum allowed font size
423
+ presentation_id: Presentation ID (uses current if None)
424
+ """
425
+ pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
426
+
427
+ if pres_id is None or pres_id not in presentations:
428
+ return {
429
+ "error": "No presentation is currently loaded or the specified ID is invalid"
430
+ }
431
+
432
+ pres = presentations[pres_id]
433
+
434
+ if slide_index < 0 or slide_index >= len(pres.slides):
435
+ return {
436
+ "error": f"Invalid slide index: {slide_index}. Available slides: 0-{len(pres.slides) - 1}"
437
+ }
438
+
439
+ slide = pres.slides[slide_index]
440
+
441
+ try:
442
+ optimizations_applied = []
443
+ manager = template_utils.get_enhanced_template_manager()
444
+
445
+ # Analyze each text shape on the slide
446
+ for i, shape in enumerate(slide.shapes):
447
+ if hasattr(shape, 'text_frame') and shape.text_frame.text:
448
+ text = shape.text_frame.text
449
+
450
+ # Calculate container dimensions
451
+ container_width = shape.width.inches
452
+ container_height = shape.height.inches
453
+
454
+ shape_optimizations = []
455
+
456
+ # Apply auto-resize if enabled
457
+ if auto_resize:
458
+ optimal_size = template_utils.calculate_dynamic_font_size(
459
+ text, container_width, container_height
460
+ )
461
+ optimal_size = max(min_font_size, min(max_font_size, optimal_size))
462
+
463
+ # Apply the calculated font size
464
+ for paragraph in shape.text_frame.paragraphs:
465
+ for run in paragraph.runs:
466
+ run.font.size = template_utils.Pt(optimal_size)
467
+
468
+ shape_optimizations.append(f"Font resized to {optimal_size}pt")
469
+
470
+ # Apply auto-wrap if enabled
471
+ if auto_wrap:
472
+ current_font_size = 14 # Default assumption
473
+ if shape.text_frame.paragraphs and shape.text_frame.paragraphs[0].runs:
474
+ if shape.text_frame.paragraphs[0].runs[0].font.size:
475
+ current_font_size = shape.text_frame.paragraphs[0].runs[0].font.size.pt
476
+
477
+ wrapped_text = template_utils.wrap_text_automatically(
478
+ text, container_width, current_font_size
479
+ )
480
+
481
+ if wrapped_text != text:
482
+ shape.text_frame.text = wrapped_text
483
+ shape_optimizations.append("Text wrapped automatically")
484
+
485
+ # Optimize spacing if enabled
486
+ if optimize_spacing:
487
+ text_length = len(text)
488
+ if text_length > 300:
489
+ line_spacing = 1.4
490
+ elif text_length > 150:
491
+ line_spacing = 1.3
492
+ else:
493
+ line_spacing = 1.2
494
+
495
+ for paragraph in shape.text_frame.paragraphs:
496
+ paragraph.line_spacing = line_spacing
497
+
498
+ shape_optimizations.append(f"Line spacing set to {line_spacing}")
499
+
500
+ if shape_optimizations:
501
+ optimizations_applied.append({
502
+ "shape_index": i,
503
+ "optimizations": shape_optimizations
504
+ })
505
+
506
+ return {
507
+ "message": f"Optimized {len(optimizations_applied)} text elements on slide {slide_index}",
508
+ "slide_index": slide_index,
509
+ "optimizations_applied": optimizations_applied,
510
+ "settings": {
511
+ "auto_resize": auto_resize,
512
+ "auto_wrap": auto_wrap,
513
+ "optimize_spacing": optimize_spacing,
514
+ "font_size_range": f"{min_font_size}-{max_font_size}pt"
515
+ }
516
+ }
517
+
518
+ except Exception as e:
519
+ return {
520
+ "error": f"Failed to optimize slide text: {str(e)}"
521
521
  }