template_tools.pyā¢23.5 kB
"""
Enhanced template-based slide creation tools for PowerPoint MCP Server.
Handles template application, template management, automated slide generation,
and advanced features like dynamic sizing, auto-wrapping, and visual effects.
"""
from typing import Dict, List, Optional, Any
from mcp.server.fastmcp import FastMCP
import utils.template_utils as template_utils
def register_template_tools(app: FastMCP, presentations: Dict, get_current_presentation_id):
"""Register template-based tools with the FastMCP app"""
@app.tool()
def list_slide_templates() -> Dict:
"""List all available slide layout templates."""
try:
available_templates = template_utils.get_available_templates()
usage_examples = template_utils.get_template_usage_examples()
return {
"available_templates": available_templates,
"total_templates": len(available_templates),
"usage_examples": usage_examples,
"message": "Use apply_slide_template to apply templates to slides"
}
except Exception as e:
return {
"error": f"Failed to list templates: {str(e)}"
}
@app.tool()
def apply_slide_template(
slide_index: int,
template_id: str,
color_scheme: str = "modern_blue",
content_mapping: Optional[Dict[str, str]] = None,
image_paths: Optional[Dict[str, str]] = None,
presentation_id: Optional[str] = None
) -> Dict:
"""
Apply a structured layout template to an existing slide.
This modifies slide layout and content structure using predefined templates.
Args:
slide_index: Index of the slide to apply template to
template_id: ID of the template to apply (e.g., 'title_slide', 'text_with_image')
color_scheme: Color scheme to use ('modern_blue', 'corporate_gray', 'elegant_green', 'warm_red')
content_mapping: Dictionary mapping element roles to custom content
image_paths: Dictionary mapping image element roles to file paths
presentation_id: Presentation ID (uses current if None)
"""
pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
if pres_id is None or pres_id not in presentations:
return {
"error": "No presentation is currently loaded or the specified ID is invalid"
}
pres = presentations[pres_id]
if slide_index < 0 or slide_index >= len(pres.slides):
return {
"error": f"Invalid slide index: {slide_index}. Available slides: 0-{len(pres.slides) - 1}"
}
slide = pres.slides[slide_index]
try:
result = template_utils.apply_slide_template(
slide, template_id, color_scheme,
content_mapping or {}, image_paths or {}
)
if result['success']:
return {
"message": f"Applied template '{template_id}' to slide {slide_index}",
"slide_index": slide_index,
"template_applied": result
}
else:
return {
"error": f"Failed to apply template: {result.get('error', 'Unknown error')}"
}
except Exception as e:
return {
"error": f"Failed to apply template: {str(e)}"
}
@app.tool()
def create_slide_from_template(
template_id: str,
color_scheme: str = "modern_blue",
content_mapping: Optional[Dict[str, str]] = None,
image_paths: Optional[Dict[str, str]] = None,
layout_index: int = 1,
presentation_id: Optional[str] = None
) -> Dict:
"""
Create a new slide using a layout template.
Args:
template_id: ID of the template to use (e.g., 'title_slide', 'text_with_image')
color_scheme: Color scheme to use ('modern_blue', 'corporate_gray', 'elegant_green', 'warm_red')
content_mapping: Dictionary mapping element roles to custom content
image_paths: Dictionary mapping image element roles to file paths
layout_index: PowerPoint layout index to use as base (default: 1)
presentation_id: Presentation ID (uses current if None)
"""
pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
if pres_id is None or pres_id not in presentations:
return {
"error": "No presentation is currently loaded or the specified ID is invalid"
}
pres = presentations[pres_id]
# Validate layout index
if layout_index < 0 or layout_index >= len(pres.slide_layouts):
return {
"error": f"Invalid layout index: {layout_index}. Available layouts: 0-{len(pres.slide_layouts) - 1}"
}
try:
# Add new slide
layout = pres.slide_layouts[layout_index]
slide = pres.slides.add_slide(layout)
slide_index = len(pres.slides) - 1
# Apply template
result = template_utils.apply_slide_template(
slide, template_id, color_scheme,
content_mapping or {}, image_paths or {}
)
if result['success']:
return {
"message": f"Created slide {slide_index} using template '{template_id}'",
"slide_index": slide_index,
"template_applied": result
}
else:
return {
"error": f"Failed to apply template to new slide: {result.get('error', 'Unknown error')}"
}
except Exception as e:
return {
"error": f"Failed to create slide from template: {str(e)}"
}
@app.tool()
def create_presentation_from_templates(
template_sequence: List[Dict[str, Any]],
color_scheme: str = "modern_blue",
presentation_title: Optional[str] = None,
presentation_id: Optional[str] = None
) -> Dict:
"""
Create a complete presentation from a sequence of templates.
Args:
template_sequence: List of template configurations, each containing:
- template_id: Template to use
- content: Content mapping for the template
- images: Image path mapping for the template
color_scheme: Color scheme to apply to all slides
presentation_title: Optional title for the presentation
presentation_id: Presentation ID (uses current if None)
Example template_sequence:
[
{
"template_id": "title_slide",
"content": {
"title": "My Presentation",
"subtitle": "Annual Report 2024",
"author": "John Doe"
}
},
{
"template_id": "text_with_image",
"content": {
"title": "Key Results",
"content": "⢠Achievement 1\\n⢠Achievement 2"
},
"images": {
"supporting": "/path/to/image.jpg"
}
}
]
"""
pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
if pres_id is None or pres_id not in presentations:
return {
"error": "No presentation is currently loaded or the specified ID is invalid"
}
pres = presentations[pres_id]
if not template_sequence:
return {
"error": "Template sequence cannot be empty"
}
try:
# Set presentation title if provided
if presentation_title:
pres.core_properties.title = presentation_title
# Create slides from template sequence
result = template_utils.create_presentation_from_template_sequence(
pres, template_sequence, color_scheme
)
if result['success']:
return {
"message": f"Created presentation with {result['total_slides']} slides",
"presentation_id": pres_id,
"creation_result": result,
"total_slides": len(pres.slides)
}
else:
return {
"warning": "Presentation created with some errors",
"presentation_id": pres_id,
"creation_result": result,
"total_slides": len(pres.slides)
}
except Exception as e:
return {
"error": f"Failed to create presentation from templates: {str(e)}"
}
@app.tool()
def get_template_info(template_id: str) -> Dict:
"""
Get detailed information about a specific template.
Args:
template_id: ID of the template to get information about
"""
try:
templates_data = template_utils.load_slide_templates()
if template_id not in templates_data.get('templates', {}):
available_templates = list(templates_data.get('templates', {}).keys())
return {
"error": f"Template '{template_id}' not found",
"available_templates": available_templates
}
template = templates_data['templates'][template_id]
# Extract element information
elements_info = []
for element in template.get('elements', []):
element_info = {
"type": element.get('type'),
"role": element.get('role'),
"position": element.get('position'),
"placeholder_text": element.get('placeholder_text', ''),
"styling_options": list(element.get('styling', {}).keys())
}
elements_info.append(element_info)
return {
"template_id": template_id,
"name": template.get('name'),
"description": template.get('description'),
"layout_type": template.get('layout_type'),
"elements": elements_info,
"element_count": len(elements_info),
"has_background": 'background' in template,
"background_type": template.get('background', {}).get('type'),
"color_schemes": list(templates_data.get('color_schemes', {}).keys()),
"usage_tip": f"Use create_slide_from_template with template_id='{template_id}' to create a slide with this layout"
}
except Exception as e:
return {
"error": f"Failed to get template info: {str(e)}"
}
@app.tool()
def auto_generate_presentation(
topic: str,
slide_count: int = 5,
presentation_type: str = "business",
color_scheme: str = "modern_blue",
include_charts: bool = True,
include_images: bool = False,
presentation_id: Optional[str] = None
) -> Dict:
"""
Automatically generate a presentation based on topic and preferences.
Args:
topic: Main topic/theme for the presentation
slide_count: Number of slides to generate (3-20)
presentation_type: Type of presentation ('business', 'academic', 'creative')
color_scheme: Color scheme to use
include_charts: Whether to include chart slides
include_images: Whether to include image placeholders
presentation_id: Presentation ID (uses current if None)
"""
pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
if pres_id is None or pres_id not in presentations:
return {
"error": "No presentation is currently loaded or the specified ID is invalid"
}
if slide_count < 3 or slide_count > 20:
return {
"error": "Slide count must be between 3 and 20"
}
try:
# Define presentation structures based on type
if presentation_type == "business":
base_templates = [
("title_slide", {"title": f"{topic}", "subtitle": "Executive Presentation", "author": "Business Team"}),
("agenda_slide", {"agenda_items": "1. Executive Summary\n\n2. Current Situation\n\n3. Analysis & Insights\n\n4. Recommendations\n\n5. Next Steps"}),
("key_metrics_dashboard", {"title": "Key Performance Indicators"}),
("text_with_image", {"title": "Current Situation", "content": f"Overview of {topic}:\n⢠Current status\n⢠Key challenges\n⢠Market position"}),
("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"}),
]
if include_charts:
base_templates.append(("chart_comparison", {"title": "Performance Comparison"}))
base_templates.append(("thank_you_slide", {"contact": "Thank you for your attention\nQuestions & Discussion"}))
elif presentation_type == "academic":
base_templates = [
("title_slide", {"title": f"Research on {topic}", "subtitle": "Academic Study", "author": "Research Team"}),
("agenda_slide", {"agenda_items": "1. Introduction\n\n2. Literature Review\n\n3. Methodology\n\n4. Results\n\n5. Conclusions"}),
("text_with_image", {"title": "Introduction", "content": f"Research focus on {topic}:\n⢠Background\n⢠Problem statement\n⢠Research questions"}),
("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"}),
("data_table_slide", {"title": "Results Summary"}),
]
if include_charts:
base_templates.append(("chart_comparison", {"title": "Data Analysis"}))
base_templates.append(("thank_you_slide", {"contact": "Questions & Discussion\nContact: research@university.edu"}))
else: # creative
base_templates = [
("title_slide", {"title": f"Creative Vision: {topic}", "subtitle": "Innovative Concepts", "author": "Creative Team"}),
("full_image_slide", {"overlay_title": f"Exploring {topic}", "overlay_subtitle": "Creative possibilities"}),
("three_column_layout", {"title": "Creative Concepts"}),
("quote_testimonial", {"quote_text": f"Innovation in {topic} requires thinking beyond conventional boundaries", "attribution": "ā Creative Director"}),
("process_flow", {"title": "Creative Process"}),
]
if include_charts:
base_templates.append(("key_metrics_dashboard", {"title": "Impact Metrics"}))
base_templates.append(("thank_you_slide", {"contact": "Let's create something amazing together\ncreative@studio.com"}))
# Adjust templates to match requested slide count
template_sequence = []
templates_to_use = base_templates[:slide_count]
# If we need more slides, add content slides
while len(templates_to_use) < slide_count:
if include_images:
templates_to_use.insert(-1, ("text_with_image", {"title": f"{topic} - Additional Topic", "content": "⢠Key point\n⢠Supporting detail\n⢠Additional insight"}))
else:
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"}))
# Convert to proper template sequence format
for i, (template_id, content) in enumerate(templates_to_use):
template_config = {
"template_id": template_id,
"content": content
}
template_sequence.append(template_config)
# Create the presentation
result = template_utils.create_presentation_from_template_sequence(
presentations[pres_id], template_sequence, color_scheme
)
return {
"message": f"Auto-generated {slide_count}-slide presentation on '{topic}'",
"topic": topic,
"presentation_type": presentation_type,
"color_scheme": color_scheme,
"slide_count": slide_count,
"generation_result": result,
"templates_used": [t[0] for t in templates_to_use]
}
except Exception as e:
return {
"error": f"Failed to auto-generate presentation: {str(e)}"
}
# Text optimization tools
@app.tool()
def optimize_slide_text(
slide_index: int,
auto_resize: bool = True,
auto_wrap: bool = True,
optimize_spacing: bool = True,
min_font_size: int = 8,
max_font_size: int = 36,
presentation_id: Optional[str] = None
) -> Dict:
"""
Optimize text elements on a slide for better readability and fit.
Args:
slide_index: Index of the slide to optimize
auto_resize: Whether to automatically resize fonts to fit containers
auto_wrap: Whether to apply intelligent text wrapping
optimize_spacing: Whether to optimize line spacing
min_font_size: Minimum allowed font size
max_font_size: Maximum allowed font size
presentation_id: Presentation ID (uses current if None)
"""
pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
if pres_id is None or pres_id not in presentations:
return {
"error": "No presentation is currently loaded or the specified ID is invalid"
}
pres = presentations[pres_id]
if slide_index < 0 or slide_index >= len(pres.slides):
return {
"error": f"Invalid slide index: {slide_index}. Available slides: 0-{len(pres.slides) - 1}"
}
slide = pres.slides[slide_index]
try:
optimizations_applied = []
manager = template_utils.get_enhanced_template_manager()
# Analyze each text shape on the slide
for i, shape in enumerate(slide.shapes):
if hasattr(shape, 'text_frame') and shape.text_frame.text:
text = shape.text_frame.text
# Calculate container dimensions
container_width = shape.width.inches
container_height = shape.height.inches
shape_optimizations = []
# Apply auto-resize if enabled
if auto_resize:
optimal_size = template_utils.calculate_dynamic_font_size(
text, container_width, container_height
)
optimal_size = max(min_font_size, min(max_font_size, optimal_size))
# Apply the calculated font size
for paragraph in shape.text_frame.paragraphs:
for run in paragraph.runs:
run.font.size = template_utils.Pt(optimal_size)
shape_optimizations.append(f"Font resized to {optimal_size}pt")
# Apply auto-wrap if enabled
if auto_wrap:
current_font_size = 14 # Default assumption
if shape.text_frame.paragraphs and shape.text_frame.paragraphs[0].runs:
if shape.text_frame.paragraphs[0].runs[0].font.size:
current_font_size = shape.text_frame.paragraphs[0].runs[0].font.size.pt
wrapped_text = template_utils.wrap_text_automatically(
text, container_width, current_font_size
)
if wrapped_text != text:
shape.text_frame.text = wrapped_text
shape_optimizations.append("Text wrapped automatically")
# Optimize spacing if enabled
if optimize_spacing:
text_length = len(text)
if text_length > 300:
line_spacing = 1.4
elif text_length > 150:
line_spacing = 1.3
else:
line_spacing = 1.2
for paragraph in shape.text_frame.paragraphs:
paragraph.line_spacing = line_spacing
shape_optimizations.append(f"Line spacing set to {line_spacing}")
if shape_optimizations:
optimizations_applied.append({
"shape_index": i,
"optimizations": shape_optimizations
})
return {
"message": f"Optimized {len(optimizations_applied)} text elements on slide {slide_index}",
"slide_index": slide_index,
"optimizations_applied": optimizations_applied,
"settings": {
"auto_resize": auto_resize,
"auto_wrap": auto_wrap,
"optimize_spacing": optimize_spacing,
"font_size_range": f"{min_font_size}-{max_font_size}pt"
}
}
except Exception as e:
return {
"error": f"Failed to optimize slide text: {str(e)}"
}