import os from pptx import Presentation from pptx.util import Inches from PIL import Image from utils import remove_all_slides from logger import LOG # 引入日志模块 def format_text(paragraph, text): """ 格式化文本,处理加粗内容。假设 ** 包围的文本表示需要加粗。 """ while '**' in text: start = text.find('**') end = text.find('**', start + 2) if start != -1 and end != -1: # 添加加粗之前的普通文本 if start > 0: run = paragraph.add_run() run.text = text[:start] # 添加加粗文本 bold_run = paragraph.add_run() bold_run.text = text[start + 2:end] bold_run.font.bold = True # 设置加粗 # 处理剩余文本 text = text[end + 2:] else: break # 添加剩余的普通文本 if text: run = paragraph.add_run() run.text = text def insert_image_centered_in_placeholder(new_slide, image_path): """ 将图片插入到 Slide 中,使其中心与 placeholder 的中心对齐。 如果图片尺寸超过 placeholder,则进行缩小适配。 在插入成功后删除 placeholder。 """ # 构建图片的绝对路径 image_full_path = os.path.join(os.getcwd(), image_path) # 检查图片是否存在 if not os.path.exists(image_full_path): LOG.warning(f"图片路径 '{image_full_path}' 不存在,跳过此图片。") return # 打开图片并获取其大小(以像素为单位) with Image.open(image_full_path) as img: img_width_px, img_height_px = img.size # 遍历找到图片的 placeholder(type 18 表示图片 placeholder) for shape in new_slide.placeholders: if shape.placeholder_format.type == 18: placeholder_width = shape.width placeholder_height = shape.height placeholder_left = shape.left placeholder_top = shape.top # 计算 placeholder 的中心点 placeholder_center_x = placeholder_left + placeholder_width / 2 placeholder_center_y = placeholder_top + placeholder_height / 2 # 图片的宽度和高度转换为 PowerPoint 的单位 (Inches) img_width = Inches(img_width_px / 96) # 假设图片 DPI 为 96 img_height = Inches(img_height_px / 96) # 如果图片的宽度或高度超过 placeholder,按比例缩放图片 if img_width > placeholder_width or img_height > placeholder_height: scale = min(placeholder_width / img_width, placeholder_height / img_height) img_width *= scale img_height *= scale # 计算图片左上角位置,使其中心对准 placeholder 中心 left = placeholder_center_x - img_width / 2 top = placeholder_center_y - img_height / 2 # 插入图片到指定位置并设定缩放后的大小 new_slide.shapes.add_picture(image_full_path, left, top, width=img_width, height=img_height) LOG.debug(f"图片已插入,并以 placeholder 中心对齐,路径: {image_full_path}") # 移除占位符 sp = shape._element # 获取占位符的 XML 元素 sp.getparent().remove(sp) # 从父元素中删除 LOG.debug("已删除图片的 placeholder") break # 生成 PowerPoint 演示文稿 def generate_presentation(powerpoint_data, template_path: str, output_path: str): # 检查模板文件是否存在 if not os.path.exists(template_path): LOG.error(f"模板文件 '{template_path}' 不存在。") # 记录错误日志 raise FileNotFoundError(f"模板文件 '{template_path}' 不存在。") prs = Presentation(template_path) # 加载 PowerPoint 模板 remove_all_slides(prs) # 清除模板中的所有幻灯片 prs.core_properties.title = powerpoint_data.title # 设置 PowerPoint 的核心标题 # 遍历所有幻灯片数据,生成对应的 PowerPoint 幻灯片 for slide in powerpoint_data.slides: # 确保布局索引不超出范围,超出则使用默认布局 if slide.layout_id >= len(prs.slide_layouts): slide_layout = prs.slide_layouts[0] else: slide_layout = prs.slide_layouts[slide.layout_id] new_slide = prs.slides.add_slide(slide_layout) # 添加新的幻灯片 # 设置幻灯片标题 if new_slide.shapes.title: new_slide.shapes.title.text = slide.content.title LOG.debug(f"设置幻灯片标题: {slide.content.title}") # 添加文本内容 for shape in new_slide.shapes: # 只处理非标题的文本框 if shape.has_text_frame and not shape == new_slide.shapes.title: text_frame = shape.text_frame text_frame.clear() # 清除原有内容 # 直接使用第一个段落,不添加新的段落,避免额外空行 first_paragraph = text_frame.paragraphs[0] # 将要点内容作为项目符号列表添加到文本框中 for point in slide.content.bullet_points: # 第一个要点覆盖初始段落,其他要点添加新段落 paragraph = first_paragraph if point == slide.content.bullet_points[0] else text_frame.add_paragraph() paragraph.level = point["level"] # 设置项目符号的级别 format_text(paragraph, point["text"]) # 调用 format_text 方法来处理加粗文本 LOG.debug(f"添加列表项: {paragraph.text},级别: {paragraph.level}") break # 插入图片 if slide.content.image_path: insert_image_centered_in_placeholder(new_slide, slide.content.image_path) # 保存生成的 PowerPoint 文件 prs.save(output_path) LOG.info(f"演示文稿已保存到 '{output_path}'")