import os from typing import List, Dict, Union from pptx import Presentation from pptx.util import Inches, Pt from pptx.enum.text import PP_ALIGN from pptx.dml.color import RGBColor from pptx.shapes.base import BaseShape class SlideDeck: """ A class to create and manage PowerPoint presentations. """ def __init__(self, output_folder: str = "generated"): """ Initialize the SlideDeck class. Args: output_folder (str): The folder where the presentation will be saved. """ self.prs = Presentation() self.output_folder = output_folder def add_slide(self, slide_data: Dict[str, Union[str, List[str], List[List[str]]]]) -> None: """ Add a new slide to the presentation. Args: slide_data (Dict): A dictionary containing the slide content. """ prs = self.prs bullet_slide_layout = prs.slide_layouts[1] slide = prs.slides.add_slide(bullet_slide_layout) shapes = slide.shapes # Add title title_shape = shapes.title title_shape.text = slide_data.get("title_text", "") # Add body text if "text" in slide_data: body_shape = shapes.placeholders[1] tf = body_shape.text_frame for bullet in slide_data.get("text", []): p = tf.add_paragraph() p.text = bullet p.level = 0 if "p1" in slide_data: p = tf.add_paragraph() p.text = slide_data.get("p1", "") p.level = 1 # Add images if "img_path" in slide_data: cur_left = 6 for img_path in slide_data.get("img_path", []): try: top = Inches(2) left = Inches(cur_left) height = Inches(4) pic = slide.shapes.add_picture(img_path, left, top, height=height) cur_left += 1 except FileNotFoundError: print(f"Warning: Image file not found: {img_path}") # Add table if "table" in slide_data: self.add_table(slide, slide_data["table"]) def add_title_slide(self, title_page_data: Dict[str, str]) -> None: """ Add a title slide to the presentation. Args: title_page_data (Dict): A dictionary containing the title slide content. """ prs = self.prs title_slide_layout = prs.slide_layouts[0] slide = prs.slides.add_slide(title_slide_layout) title = slide.shapes.title subtitle = slide.placeholders[1] if "title_text" in title_page_data: title.text = title_page_data.get("title_text") if "subtitle_text" in title_page_data: subtitle.text = title_page_data.get("subtitle_text") def add_table(self, slide, table_data: List[List[str]]) -> None: """ Add a table to the given slide. Args: slide (Slide): The slide to add the table to. table_data (List[List[str]]): The data for the table. """ # Determine the maximum number of columns max_cols = max(len(row) for row in table_data) rows = len(table_data) left = Inches(0.5) top = Inches(1.5) width = Inches(9) height = Inches(5.5) table = slide.shapes.add_table(rows, max_cols, left, top, width, height).table # Set column widths first_col_width = 3.5 remaining_width = 9 - first_col_width other_col_width = remaining_width / (max_cols - 1) if max_cols > 1 else remaining_width table.columns[0].width = Inches(first_col_width) for i in range(1, max_cols): table.columns[i].width = Inches(other_col_width) # Populate the table with data for i, row in enumerate(table_data): if len(row) < max_cols: row.insert(0, " ") for j, cell in enumerate(row): table.cell(i, j).text = str(cell) paragraph = table.cell(i, j).text_frame.paragraphs[0] paragraph.font.size = Pt(10) paragraph.alignment = PP_ALIGN.CENTER if j > 0 or i == 0 else PP_ALIGN.LEFT # Style the header row for cell in table.rows[0].cells: cell.fill.solid() cell.fill.fore_color.rgb = RGBColor(0, 112, 192) # Blue color cell.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255) # White text cell.text_frame.paragraphs[0].font.bold = True # Style the first column for i in range(1, rows): cell = table.cell(i, 0) cell.fill.solid() cell.fill.fore_color.rgb = RGBColor(230, 230, 230) # Light gray cell.text_frame.paragraphs[0].font.bold = True def create_presentation(self, title_slide_info: Dict[str, str], slide_pages_data: List[Dict[str, Union[str, List[str], List[List[str]]]]] = []) -> str: """ Create a complete presentation. Args: title_slide_info (Dict): Information for the title slide. slide_pages_data (List[Dict]): Data for all other slides. Returns: str: The file path of the saved presentation. Raises: OSError: If there's an error creating or saving the file. ValueError: If the input data is invalid. """ try: # Generate file name from title file_name = title_slide_info.get("title_text", "presentation").\ lower().replace(",", "").replace(":", "").replace(" ", "-") file_name += ".pptx" file_name = os.path.join(self.output_folder, file_name) # Create output folder if it doesn't exist os.makedirs(self.output_folder, exist_ok=True) # Add title slide self.add_title_slide(title_slide_info) # Add content slides for slide_data in slide_pages_data: self.add_slide(slide_data) # Save the presentation self.prs.save(file_name) return file_name except OSError as e: raise OSError(f"Error creating or saving the presentation: {str(e)}") except ValueError as e: raise ValueError(f"Invalid input data: {str(e)}") except Exception as e: raise Exception(f"An unexpected error occurred: {str(e)}")