Skip to main content
Glama

PowerPoint MCP Server

content_utils.py•19.7 kB
""" Content management utilities for PowerPoint MCP Server. Functions for slides, text, images, tables, charts, and shapes. """ from pptx import Presentation from pptx.chart.data import CategoryChartData from pptx.enum.chart import XL_CHART_TYPE from pptx.enum.text import PP_ALIGN from pptx.util import Inches, Pt from pptx.dml.color import RGBColor from typing import Dict, List, Tuple, Optional, Any import tempfile import os import base64 def add_slide(presentation: Presentation, layout_index: int = 1) -> Tuple: """ Add a slide to the presentation. Args: presentation: The Presentation object layout_index: Index of the slide layout to use Returns: A tuple containing the slide and its layout """ layout = presentation.slide_layouts[layout_index] slide = presentation.slides.add_slide(layout) return slide, layout def get_slide_info(slide, slide_index: int) -> Dict: """ Get information about a specific slide. Args: slide: The slide object slide_index: Index of the slide Returns: Dictionary containing slide information """ try: placeholders = [] for placeholder in slide.placeholders: placeholder_info = { "idx": placeholder.placeholder_format.idx, "type": str(placeholder.placeholder_format.type), "name": placeholder.name } placeholders.append(placeholder_info) shapes = [] for i, shape in enumerate(slide.shapes): shape_info = { "index": i, "name": shape.name, "shape_type": str(shape.shape_type), "left": shape.left, "top": shape.top, "width": shape.width, "height": shape.height } shapes.append(shape_info) return { "slide_index": slide_index, "layout_name": slide.slide_layout.name, "placeholder_count": len(placeholders), "placeholders": placeholders, "shape_count": len(shapes), "shapes": shapes } except Exception as e: raise Exception(f"Failed to get slide info: {str(e)}") def set_title(slide, title: str) -> None: """ Set the title of a slide. Args: slide: The slide object title: The title text """ if slide.shapes.title: slide.shapes.title.text = title def populate_placeholder(slide, placeholder_idx: int, text: str) -> None: """ Populate a placeholder with text. Args: slide: The slide object placeholder_idx: The index of the placeholder text: The text to add """ placeholder = slide.placeholders[placeholder_idx] placeholder.text = text def add_bullet_points(placeholder, bullet_points: List[str]) -> None: """ Add bullet points to a placeholder. Args: placeholder: The placeholder object bullet_points: List of bullet point texts """ text_frame = placeholder.text_frame text_frame.clear() for i, point in enumerate(bullet_points): p = text_frame.add_paragraph() p.text = point p.level = 0 def add_textbox(slide, left: float, top: float, width: float, height: float, text: str, font_size: int = None, font_name: str = None, bold: bool = None, italic: bool = None, underline: bool = None, color: Tuple[int, int, int] = None, bg_color: Tuple[int, int, int] = None, alignment: str = None, vertical_alignment: str = None, auto_fit: bool = True) -> Any: """ Add a textbox to a slide with formatting options. Args: slide: The slide object left: Left position in inches top: Top position in inches width: Width in inches height: Height in inches text: Text content font_size: Font size in points font_name: Font name bold: Whether text should be bold italic: Whether text should be italic underline: Whether text should be underlined color: RGB color tuple (r, g, b) bg_color: Background RGB color tuple (r, g, b) alignment: Text alignment ('left', 'center', 'right', 'justify') vertical_alignment: Vertical alignment ('top', 'middle', 'bottom') auto_fit: Whether to auto-fit text Returns: The created textbox shape """ textbox = slide.shapes.add_textbox( Inches(left), Inches(top), Inches(width), Inches(height) ) textbox.text_frame.text = text # Apply formatting if provided if any([font_size, font_name, bold, italic, underline, color, bg_color, alignment, vertical_alignment]): format_text_advanced( textbox.text_frame, font_size=font_size, font_name=font_name, bold=bold, italic=italic, underline=underline, color=color, bg_color=bg_color, alignment=alignment, vertical_alignment=vertical_alignment ) return textbox def format_text(text_frame, font_size: int = None, font_name: str = None, bold: bool = None, italic: bool = None, color: Tuple[int, int, int] = None, alignment: str = None) -> None: """ Format text in a text frame. Args: text_frame: The text frame to format font_size: Font size in points font_name: Font name bold: Whether text should be bold italic: Whether text should be italic color: RGB color tuple (r, g, b) alignment: Text alignment ('left', 'center', 'right', 'justify') """ alignment_map = { 'left': PP_ALIGN.LEFT, 'center': PP_ALIGN.CENTER, 'right': PP_ALIGN.RIGHT, 'justify': PP_ALIGN.JUSTIFY } for paragraph in text_frame.paragraphs: if alignment and alignment in alignment_map: paragraph.alignment = alignment_map[alignment] for run in paragraph.runs: font = run.font if font_size is not None: font.size = Pt(font_size) if font_name is not None: font.name = font_name if bold is not None: font.bold = bold if italic is not None: font.italic = italic if color is not None: r, g, b = color font.color.rgb = RGBColor(r, g, b) def format_text_advanced(text_frame, font_size: int = None, font_name: str = None, bold: bool = None, italic: bool = None, underline: bool = None, color: Tuple[int, int, int] = None, bg_color: Tuple[int, int, int] = None, alignment: str = None, vertical_alignment: str = None) -> Dict: """ Advanced text formatting with comprehensive options. Args: text_frame: The text frame to format font_size: Font size in points font_name: Font name bold: Whether text should be bold italic: Whether text should be italic underline: Whether text should be underlined color: RGB color tuple (r, g, b) bg_color: Background RGB color tuple (r, g, b) alignment: Text alignment ('left', 'center', 'right', 'justify') vertical_alignment: Vertical alignment ('top', 'middle', 'bottom') Returns: Dictionary with formatting results """ result = { 'success': True, 'warnings': [] } try: alignment_map = { 'left': PP_ALIGN.LEFT, 'center': PP_ALIGN.CENTER, 'right': PP_ALIGN.RIGHT, 'justify': PP_ALIGN.JUSTIFY } # Enable text wrapping text_frame.word_wrap = True # Apply formatting to all paragraphs and runs for paragraph in text_frame.paragraphs: if alignment and alignment in alignment_map: paragraph.alignment = alignment_map[alignment] for run in paragraph.runs: font = run.font if font_size is not None: font.size = Pt(font_size) if font_name is not None: font.name = font_name if bold is not None: font.bold = bold if italic is not None: font.italic = italic if underline is not None: font.underline = underline if color is not None: r, g, b = color font.color.rgb = RGBColor(r, g, b) return result except Exception as e: result['success'] = False result['error'] = str(e) return result def add_image(slide, image_path: str, left: float, top: float, width: float = None, height: float = None) -> Any: """ Add an image to a slide. Args: slide: The slide object image_path: Path to the image file left: Left position in inches top: Top position in inches width: Width in inches (optional) height: Height in inches (optional) Returns: The created image shape """ if width is not None and height is not None: return slide.shapes.add_picture( image_path, Inches(left), Inches(top), Inches(width), Inches(height) ) elif width is not None: return slide.shapes.add_picture( image_path, Inches(left), Inches(top), Inches(width) ) elif height is not None: return slide.shapes.add_picture( image_path, Inches(left), Inches(top), height=Inches(height) ) else: return slide.shapes.add_picture( image_path, Inches(left), Inches(top) ) def add_table(slide, rows: int, cols: int, left: float, top: float, width: float, height: float) -> Any: """ Add a table to a slide. Args: slide: The slide object rows: Number of rows cols: Number of columns left: Left position in inches top: Top position in inches width: Width in inches height: Height in inches Returns: The created table shape """ return slide.shapes.add_table( rows, cols, Inches(left), Inches(top), Inches(width), Inches(height) ) def format_table_cell(cell, font_size: int = None, font_name: str = None, bold: bool = None, italic: bool = None, color: Tuple[int, int, int] = None, bg_color: Tuple[int, int, int] = None, alignment: str = None, vertical_alignment: str = None) -> None: """ Format a table cell. Args: cell: The table cell object font_size: Font size in points font_name: Font name bold: Whether text should be bold italic: Whether text should be italic color: RGB color tuple (r, g, b) bg_color: Background RGB color tuple (r, g, b) alignment: Text alignment vertical_alignment: Vertical alignment """ # Format text if any([font_size, font_name, bold, italic, color, alignment]): format_text_advanced( cell.text_frame, font_size=font_size, font_name=font_name, bold=bold, italic=italic, color=color, alignment=alignment ) # Set background color if bg_color: cell.fill.solid() cell.fill.fore_color.rgb = RGBColor(*bg_color) def add_chart(slide, chart_type: str, left: float, top: float, width: float, height: float, categories: List[str], series_names: List[str], series_values: List[List[float]]) -> Any: """ Add a chart to a slide. Args: slide: The slide object chart_type: Type of chart ('column', 'bar', 'line', 'pie', etc.) left: Left position in inches top: Top position in inches width: Width in inches height: Height in inches categories: List of category names series_names: List of series names series_values: List of value lists for each series Returns: The created chart object """ # Map chart type names to enum values chart_type_map = { 'column': XL_CHART_TYPE.COLUMN_CLUSTERED, 'stacked_column': XL_CHART_TYPE.COLUMN_STACKED, 'bar': XL_CHART_TYPE.BAR_CLUSTERED, 'stacked_bar': XL_CHART_TYPE.BAR_STACKED, 'line': XL_CHART_TYPE.LINE, 'line_markers': XL_CHART_TYPE.LINE_MARKERS, 'pie': XL_CHART_TYPE.PIE, 'doughnut': XL_CHART_TYPE.DOUGHNUT, 'area': XL_CHART_TYPE.AREA, 'stacked_area': XL_CHART_TYPE.AREA_STACKED, 'scatter': XL_CHART_TYPE.XY_SCATTER, 'radar': XL_CHART_TYPE.RADAR, 'radar_markers': XL_CHART_TYPE.RADAR_MARKERS } xl_chart_type = chart_type_map.get(chart_type.lower(), XL_CHART_TYPE.COLUMN_CLUSTERED) # Create chart data chart_data = CategoryChartData() chart_data.categories = categories for i, series_name in enumerate(series_names): if i < len(series_values): chart_data.add_series(series_name, series_values[i]) # Add chart to slide chart_shape = slide.shapes.add_chart( xl_chart_type, Inches(left), Inches(top), Inches(width), Inches(height), chart_data ) return chart_shape.chart def format_chart(chart, has_legend: bool = True, legend_position: str = 'right', has_data_labels: bool = False, title: str = None, x_axis_title: str = None, y_axis_title: str = None, color_scheme: str = None) -> None: """ Format a chart with various options. Args: chart: The chart object has_legend: Whether to show legend legend_position: Position of legend ('right', 'top', 'bottom', 'left') has_data_labels: Whether to show data labels title: Chart title x_axis_title: X-axis title y_axis_title: Y-axis title color_scheme: Color scheme to apply """ try: # Set chart title if title: chart.chart_title.text_frame.text = title # Configure legend if has_legend: chart.has_legend = True # Note: Legend position setting may vary by chart type else: chart.has_legend = False # Configure data labels if has_data_labels: for series in chart.series: series.has_data_labels = True # Set axis titles if available try: if x_axis_title and hasattr(chart, 'category_axis'): chart.category_axis.axis_title.text_frame.text = x_axis_title if y_axis_title and hasattr(chart, 'value_axis'): chart.value_axis.axis_title.text_frame.text = y_axis_title except: pass # Axis titles may not be available for all chart types except Exception: pass # Graceful degradation for chart formatting def extract_slide_text_content(slide) -> Dict: """ Extract all text content from a slide including placeholders and text shapes. Args: slide: The slide object to extract text from Returns: Dictionary containing all text content organized by source type """ try: text_content = { "slide_title": "", "placeholders": [], "text_shapes": [], "table_text": [], "all_text_combined": "" } all_texts = [] # Extract title from slide if available if hasattr(slide, 'shapes') and hasattr(slide.shapes, 'title') and slide.shapes.title: try: title_text = slide.shapes.title.text_frame.text.strip() if title_text: text_content["slide_title"] = title_text all_texts.append(title_text) except: pass # Extract text from all shapes for i, shape in enumerate(slide.shapes): shape_text_info = { "shape_index": i, "shape_name": shape.name, "shape_type": str(shape.shape_type), "text": "" } try: # Check if shape has text frame if hasattr(shape, 'text_frame') and shape.text_frame: text = shape.text_frame.text.strip() if text: shape_text_info["text"] = text all_texts.append(text) # Categorize by shape type if hasattr(shape, 'placeholder_format'): # This is a placeholder placeholder_info = shape_text_info.copy() placeholder_info["placeholder_type"] = str(shape.placeholder_format.type) placeholder_info["placeholder_idx"] = shape.placeholder_format.idx text_content["placeholders"].append(placeholder_info) else: # This is a regular text shape text_content["text_shapes"].append(shape_text_info) # Extract text from tables elif hasattr(shape, 'table'): table_texts = [] table = shape.table for row_idx, row in enumerate(table.rows): row_texts = [] for col_idx, cell in enumerate(row.cells): cell_text = cell.text_frame.text.strip() if cell_text: row_texts.append(cell_text) all_texts.append(cell_text) if row_texts: table_texts.append({ "row": row_idx, "cells": row_texts }) if table_texts: text_content["table_text"].append({ "shape_index": i, "shape_name": shape.name, "table_content": table_texts }) except Exception as e: # Skip shapes that can't be processed continue # Combine all text text_content["all_text_combined"] = "\n".join(all_texts) return { "success": True, "text_content": text_content, "total_text_shapes": len(text_content["placeholders"]) + len(text_content["text_shapes"]), "has_title": bool(text_content["slide_title"]), "has_tables": len(text_content["table_text"]) > 0 } except Exception as e: return { "success": False, "error": f"Failed to extract text content: {str(e)}", "text_content": None }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GongRzhe/Office-PowerPoint-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server