mcpcn-office-powerpoint-mcp-server 2.0.7__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.0.7.dist-info/METADATA +1023 -0
- mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/RECORD +25 -0
- mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/WHEEL +4 -0
- mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/entry_points.txt +2 -0
- mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/licenses/LICENSE +21 -0
- ppt_mcp_server.py +450 -0
- slide_layout_templates.json +3690 -0
- tools/__init__.py +28 -0
- tools/chart_tools.py +82 -0
- tools/connector_tools.py +91 -0
- tools/content_tools.py +593 -0
- tools/hyperlink_tools.py +138 -0
- tools/master_tools.py +114 -0
- tools/presentation_tools.py +212 -0
- tools/professional_tools.py +290 -0
- tools/structural_tools.py +373 -0
- tools/template_tools.py +521 -0
- tools/transition_tools.py +75 -0
- utils/__init__.py +69 -0
- utils/content_utils.py +579 -0
- utils/core_utils.py +55 -0
- utils/design_utils.py +689 -0
- utils/presentation_utils.py +217 -0
- utils/template_utils.py +1143 -0
- utils/validation_utils.py +323 -0
tools/template_tools.py
ADDED
|
@@ -0,0 +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)}"
|
|
521
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Slide transition management tools for PowerPoint MCP Server.
|
|
3
|
+
Implements slide transition and timing capabilities.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, List, Optional, Any
|
|
7
|
+
|
|
8
|
+
def register_transition_tools(app, presentations, get_current_presentation_id, validate_parameters,
|
|
9
|
+
is_positive, is_non_negative, is_in_range, is_valid_rgb):
|
|
10
|
+
"""Register slide transition management tools with the FastMCP app."""
|
|
11
|
+
|
|
12
|
+
@app.tool()
|
|
13
|
+
def manage_slide_transitions(
|
|
14
|
+
slide_index: int,
|
|
15
|
+
operation: str,
|
|
16
|
+
transition_type: str = None,
|
|
17
|
+
duration: float = 1.0,
|
|
18
|
+
presentation_id: str = None
|
|
19
|
+
) -> Dict:
|
|
20
|
+
"""
|
|
21
|
+
Manage slide transitions and timing.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
slide_index: Index of the slide (0-based)
|
|
25
|
+
operation: Operation type ("set", "remove", "get")
|
|
26
|
+
transition_type: Type of transition (basic support)
|
|
27
|
+
duration: Duration of transition in seconds
|
|
28
|
+
presentation_id: Optional presentation ID (uses current if not provided)
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dictionary with transition information
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
# Get presentation
|
|
35
|
+
pres_id = presentation_id or get_current_presentation_id()
|
|
36
|
+
if pres_id not in presentations:
|
|
37
|
+
return {"error": "Presentation not found"}
|
|
38
|
+
|
|
39
|
+
pres = presentations[pres_id]
|
|
40
|
+
|
|
41
|
+
# Validate slide index
|
|
42
|
+
if not (0 <= slide_index < len(pres.slides)):
|
|
43
|
+
return {"error": f"Slide index {slide_index} out of range"}
|
|
44
|
+
|
|
45
|
+
slide = pres.slides[slide_index]
|
|
46
|
+
|
|
47
|
+
if operation == "get":
|
|
48
|
+
# Get current transition info (limited python-pptx support)
|
|
49
|
+
return {
|
|
50
|
+
"message": f"Transition info for slide {slide_index}",
|
|
51
|
+
"slide_index": slide_index,
|
|
52
|
+
"note": "Transition reading has limited support in python-pptx"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
elif operation == "set":
|
|
56
|
+
return {
|
|
57
|
+
"message": f"Transition setting requested for slide {slide_index}",
|
|
58
|
+
"slide_index": slide_index,
|
|
59
|
+
"transition_type": transition_type,
|
|
60
|
+
"duration": duration,
|
|
61
|
+
"note": "Transition setting has limited support in python-pptx - this is a placeholder for future enhancement"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
elif operation == "remove":
|
|
65
|
+
return {
|
|
66
|
+
"message": f"Transition removal requested for slide {slide_index}",
|
|
67
|
+
"slide_index": slide_index,
|
|
68
|
+
"note": "Transition removal has limited support in python-pptx - this is a placeholder for future enhancement"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
else:
|
|
72
|
+
return {"error": f"Unsupported operation: {operation}. Use 'set', 'remove', or 'get'"}
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
return {"error": f"Failed to manage slide transitions: {str(e)}"}
|
utils/__init__.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PowerPoint utilities package.
|
|
3
|
+
Organized utility functions for PowerPoint manipulation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .core_utils import *
|
|
7
|
+
from .presentation_utils import *
|
|
8
|
+
from .content_utils import *
|
|
9
|
+
from .design_utils import *
|
|
10
|
+
from .validation_utils import *
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
# Core utilities
|
|
14
|
+
"safe_operation",
|
|
15
|
+
"try_multiple_approaches",
|
|
16
|
+
|
|
17
|
+
# Presentation utilities
|
|
18
|
+
"create_presentation",
|
|
19
|
+
"open_presentation",
|
|
20
|
+
"save_presentation",
|
|
21
|
+
"create_presentation_from_template",
|
|
22
|
+
"get_presentation_info",
|
|
23
|
+
"get_template_info",
|
|
24
|
+
"set_core_properties",
|
|
25
|
+
"get_core_properties",
|
|
26
|
+
|
|
27
|
+
# Content utilities
|
|
28
|
+
"add_slide",
|
|
29
|
+
"get_slide_info",
|
|
30
|
+
"set_title",
|
|
31
|
+
"populate_placeholder",
|
|
32
|
+
"add_bullet_points",
|
|
33
|
+
"add_textbox",
|
|
34
|
+
"format_text",
|
|
35
|
+
"format_text_advanced",
|
|
36
|
+
"add_image",
|
|
37
|
+
"add_table",
|
|
38
|
+
"format_table_cell",
|
|
39
|
+
"add_chart",
|
|
40
|
+
"format_chart",
|
|
41
|
+
|
|
42
|
+
# Design utilities
|
|
43
|
+
"get_professional_color",
|
|
44
|
+
"get_professional_font",
|
|
45
|
+
"get_color_schemes",
|
|
46
|
+
"add_professional_slide",
|
|
47
|
+
"apply_professional_theme",
|
|
48
|
+
"enhance_existing_slide",
|
|
49
|
+
"apply_professional_image_enhancement",
|
|
50
|
+
"enhance_image_with_pillow",
|
|
51
|
+
"set_slide_gradient_background",
|
|
52
|
+
"create_professional_gradient_background",
|
|
53
|
+
"format_shape",
|
|
54
|
+
"apply_picture_shadow",
|
|
55
|
+
"apply_picture_reflection",
|
|
56
|
+
"apply_picture_glow",
|
|
57
|
+
"apply_picture_soft_edges",
|
|
58
|
+
"apply_picture_rotation",
|
|
59
|
+
"apply_picture_transparency",
|
|
60
|
+
"apply_picture_bevel",
|
|
61
|
+
"apply_picture_filter",
|
|
62
|
+
"analyze_font_file",
|
|
63
|
+
"optimize_font_for_presentation",
|
|
64
|
+
"get_font_recommendations",
|
|
65
|
+
|
|
66
|
+
# Validation utilities
|
|
67
|
+
"validate_text_fit",
|
|
68
|
+
"validate_and_fix_slide"
|
|
69
|
+
]
|