File size: 6,140 Bytes
76684fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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}'")