from pptx import Presentation from pptx.util import Inches as _Inches, Pt as _Pt from pptx.dml.color import RGBColor from pptx.enum.text import PP_ALIGN, MSO_AUTO_SIZE from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, MSO_SHAPE_TYPE from io import BytesIO ARROW_ADD = '""""""' class SlideAgent: def __init__(self, slide_width=13.33, slide_height=7.5): """Initialize a new presentation with specified slide dimensions in inches.""" self.prs = Presentation() self.prs.slide_width = self._inches(slide_width) self.prs.slide_height = self._inches(slide_height) self.slide = None def _inches(self, val): """Helper method to convert to Inches.""" return _Inches(val) def _points(self, val): """Helper method to convert to Points.""" return _Pt(val) # ------- Slide APIs ------- def add_slide(self, layout=0): """Create a new slide with a specific layout.""" slide_layout = self.prs.slide_layouts[layout] self.slide = self.prs.slides.add_slide(slide_layout) # ------- Text APIs ------- def add_title(self, text, font_size=44, font_color=(0, 0, 0)): """Add a title to the slide with a custom font size (in points) and font color (RGB tuple).""" title_shape = self.slide.shapes.title title_shape.text = text self._format_text(title_shape.text_frame, self._points(font_size), RGBColor(*font_color)) def add_text(self, text, top, left, width, height, font_size=20, bold=False, color=(0, 0, 0), background_color=None, auto_size=True): """Add a text box at a specified location with custom text settings and optional background color.""" # Create the text box shape text_box = self.slide.shapes.add_textbox(self._inches(left), self._inches(top), self._inches(width), self._inches(height)) # Set background color if provided if background_color: text_box.fill.solid() text_box.fill.fore_color.rgb = RGBColor(*background_color) else: text_box.fill.background() # No fill if no color is specified # Handle line breaks and adjust height lines = text.split("\n") adjusted_height = height * len(lines) # Adjust height based on the number of lines text_box.height = self._inches(adjusted_height) # Set text and format it text_frame = text_box.text_frame text_frame.word_wrap = True if auto_size: text_frame.auto_size = MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT # Automatically fit the text box to the text self._format_paragraph(text_frame, text, self._points(font_size), bold, RGBColor(*color)) def add_bullet_points(self, bullet_points, top, left, width, height, font_size=18, color=(0, 0, 0)): """Add a text box with bullet points.""" text_box = self.slide.shapes.add_textbox(self._inches(left), self._inches(top), self._inches(width), self._inches(height)) text_frame = text_box.text_frame text_frame.word_wrap = True text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE for point in bullet_points: p = text_frame.add_paragraph() p.text = point self._format_text(p, self._points(font_size), RGBColor(*color)) p.level = bullet_points.index(point) # ------- Image APIs ------- def add_image(self, image_path, top, left, width, height): """Add an image at a specified location.""" self.slide.shapes.add_picture(image_path, self._inches(left), self._inches(top), self._inches(width), self._inches(height)) def add_image_centered(self, image_path, image_width, image_height): """Add an image centered on the slide.""" slide_width = self.prs.slide_width.inches slide_height = self.prs.slide_height.inches left = (slide_width - image_width) / 2 top = (slide_height - image_height) / 2 self.add_image(image_path, top, left, image_width, image_height) # ------- Shape APIs ------- def add_shape(self, shape_type, top, left, width, height, fill_color=None): """Add a shape to the slide, supporting MSO_AUTO_SHAPE_TYPE.""" if isinstance(shape_type, str): # Check if the shape type is a valid string, otherwise raise an error try: shape_type = getattr(MSO_AUTO_SHAPE_TYPE, shape_type.upper()) except AttributeError: raise ValueError(f"Invalid shape type: {shape_type}. Must be a valid MSO_AUTO_SHAPE_TYPE.") # Now create the shape with the validated or passed enum type shape = self.slide.shapes.add_shape(shape_type, self._inches(left), self._inches(top), self._inches(width), self._inches(height)) if fill_color: shape.fill.solid() shape.fill.fore_color.rgb = RGBColor(*fill_color) def add_straight_arrow(self, start_x, start_y, end_x, end_y): connector = self.slide.shapes.add_connector("MSO_CONNECTOR.STRAIGHT", start_x, start_y, end_x, end_y) def add_straight_line(self, start_x, start_y, end_x, end_y): connector = self.slide.shapes.add_connector("MSO_CONNECTOR.STRAIGHT", start_x, start_y, end_x, end_y) line_elem = connector.line._get_or_add_ln() line_elem.append(parse_xml({ARROW_ADD})) # ------- Table APIs ------- def add_table(self, rows, cols, top, left, width, height, column_widths=None): """Add a table to the slide.""" table = self.slide.shapes.add_table(rows, cols, left, top, width, height).table if column_widths: for idx, col_width in enumerate(column_widths): table.columns[idx].width = Inches(col_width) return table # ------- Helper APIs ------- def set_background_color(self, color): """Set background color for the current slide.""" background = self.slide.background fill = background.fill fill.solid() fill.fore_color.rgb = color def duplicate_slide(self, slide_index): """Duplicate a slide by index.""" template_slide = self.prs.slides[slide_index] new_slide = self.prs.slides.add_slide(template_slide.slide_layout) for shape in template_slide.shapes: self._copy_shape(shape, new_slide) def save_presentation(self, file_name): """Save the PowerPoint presentation.""" self.prs.save(file_name) # ------- Internal Helper Methods ------- def _format_paragraph(self, text_frame, text, font_size, bold, color): """Helper function to format text within a text frame.""" p = text_frame.add_paragraph() p.text = text p.font.size = font_size p.font.bold = bold p.font.color.rgb = color def _format_text(self, text_frame, font_size, font_color): """Helper function to format text in a text frame.""" for paragraph in text_frame.paragraphs: paragraph.font.size = font_size paragraph.font.color.rgb = font_color def _copy_shape(self, shape, slide): """Copy a shape from one slide to another.""" if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: image = BytesIO(shape.image.blob) slide.shapes.add_picture(image, shape.left, shape.top, shape.width, shape.height) elif shape.has_text_frame: new_shape = slide.shapes.add_textbox(shape.left, shape.top, shape.width, shape.height) new_shape.text = shape.text self._format_text(new_shape.text_frame, shape.text_frame.paragraphs[0].font.size, shape.text_frame.paragraphs[0].font.color.rgb)