Spaces:
Running
Running
| 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 = '"""<a:tailEnd type="arrow" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"/>"""' | |
| 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) | |