""" PPT生成核心模块 功能:将Markdown文件内容转换为PPTX格式演示文稿 模块结构: 1. PPT模板处理函数(set_开头) 2. Markdown解析函数(explain_开头) 3. 页面生成函数(gen_开头) 4. 主生成流程函数(gen_ppt_default) """ import linecache from pptx import Presentation # 操作PPTX文件的核心库 from pptx.util import Inches, Pt # 处理PPT中的尺寸单位 def set_presentation(filepath): """初始化PPT模板对象 Args: filepath (str): PPT模板文件路径 Returns: Presentation: 读取模板后的PPT对象 """ return Presentation(filepath) def set_home(presentation, title, author, title_rgb): """创建首页幻灯片 Args: presentation (Presentation): PPT对象 title (str): 主标题内容 author (str): 作者信息 title_rgb: 标题颜色RGB值 Returns: Presentation: 更新后的PPT对象 """ prs = presentation # 使用模板的第2个版式创建幻灯片(索引从0开始) # 在 PowerPoint 中:打开模板文件 → 视图 → 幻灯片母版 → 查看所有版式。 oneSlide = prs.slides.add_slide(prs.slide_layouts[1]) body_shapes = oneSlide.shapes.placeholders # 设置主标题样式 title_shape = body_shapes[0] # 标题占位符索引 title_frame = title_shape.text_frame title_paragraph = title_frame.paragraphs[0] title_run = title_paragraph.add_run() title_run.font.color.rgb = title_rgb # 设置标题颜色 title_run.text = title # 填充标题内容 # 设置作者信息(注意:placeholders[10]需要根据实际模板调整) author_shape = body_shapes[10] # 作者信息占位符索引 author_frame = author_shape.text_frame author_paragraph = author_frame.paragraphs[0] author_paragraph.text = author return prs def set_end(presentation, date_str, end_tittle_content): """创建结束页幻灯片 Args: presentation: PPT对象 date_str (str): 日期字符串 end_tittle_content (str): 结束页主文本 Returns: Presentation: 更新后的PPT对象 """ prs = presentation # 使用模板的第4个版式创建幻灯片 oneSlide = prs.slides.add_slide(prs.slide_layouts[3]) body_shapes = oneSlide.shapes.placeholders # 设置结束页主文本 title_shape = body_shapes[0] # 主文本占位符索引 title_frame = title_shape.text_frame title_paragraph = title_frame.paragraphs[0] title_paragraph.text = end_tittle_content # 设置日期信息(placeholders[13]需要根据模板调整) subtitle_shape = body_shapes[13] # 副文本占位符索引 subtitle_frame = subtitle_shape.text_frame subtitle_paragraph = subtitle_frame.paragraphs[0] subtitle_paragraph.text = date_str return prs def set_content(presentation, content_titles, current_i, content_font, current_content, other_content, current_rgb, other_rgb, content_number_rgb): """创建目录页幻灯片 Args: presentation: PPT对象 content_titles (list): 目录项列表 current_i (int): 当前高亮项的索引 content_font (str): 字体名称 current_content (str): 高亮项图标路径 other_content (str): 普通项图标路径 current_rgb: 高亮颜色RGB值 other_rgb: 普通颜色RGB值 content_number_rgb: 编号颜色RGB值 Returns: Presentation: 更新后的PPT对象 """ prs = presentation # 使用模板的第3个版式创建目录页 content_back = prs.slide_layouts[2] slide = prs.slides.add_slide(content_back) # 布局参数定义(单位:英寸) one_col_left = 4.55 # 单列布局X坐标 two_col_left1 = 2.75 # 双列左栏X坐标 two_col_left2 = 7.25 # 双列右栏X坐标 width = 3.5 # 文本框宽度 height = 0.5 # 文本框高度 # 动态计算垂直布局 tlen = len(content_titles) if tlen <= 3: height_total = height * tlen + 0.5 * (tlen - 1) # 总高度 = 项高度 + 间距 else: height_total = height * (tlen / 2) + 0.5 * (tlen / 2 - 1) top = (7.5 - height_total) / 2 # 垂直居中计算 top_flag = top # 当前项顶部位置 # 遍历添加所有目录项 for i in range(tlen): # 计算项位置 if i == 0 or (tlen >= 3 and i == int((tlen + 1) / 2)): i_top = top else: i_top = top_flag + (0.5 + height) # 累加项高度和间距 top_flag = i_top # 更新当前位置标记 # 计算水平位置 i_left = 0 if tlen <= 2: i_left = one_col_left elif tlen > 2 and i < tlen / 2: i_left = two_col_left1 elif tlen > 2 and i >= tlen / 2: i_left = two_col_left2 # 添加文本内容 txBox = slide.shapes.add_textbox(Inches(i_left + 1), Inches(i_top), Inches(width), Inches(height)) tf = txBox.text_frame title = tf.paragraphs[0] run = title.add_run() run.text = content_titles[i] # 填充目录文本 # 设置字体样式 font = run.font font.name = content_font # 字体类型 font.bold = True # 加粗 if i == current_i: # 高亮当前项 font.color.rgb = current_rgb img_path = current_content else: # 普通项 font.color.rgb = other_rgb img_path = other_content font.size = Pt(26) # 字号26磅 # 添加左侧图标 slide.shapes.add_picture(img_path, Inches(i_left), Inches(i_top)) # 添加序号 numberBox = slide.shapes.add_textbox(Inches(i_left + 0.125), Inches(i_top + 0.125), Inches(0.5), Inches(0.5)) number = numberBox.text_frame number_title = number.paragraphs[0] number_run = number_title.add_run() number_run.text = str(i + 1) # 显示序号 # 设置序号样式 number_font = number_run.font number_font.name = content_font number_font.bold = True number_font.color.rgb = content_number_rgb number_font.size = Pt(18) return prs def set_for_content(presentation, first_list, second_dict, content_list, second_title_rgb, content_font, current_content, other_content, current_rgb, other_rgb, content_number_rgb, text_img_path, import_font_size, unsort_img_path, list_font_size): """批量生成内容页 Args: presentation: PPT对象 first_list (list): 一级标题列表 second_dict (dict): 二级标题字典 content_list (dict): 内容字典 ...其他样式参数... Returns: Presentation: 更新后的PPT对象 """ prs = presentation # 遍历一级标题 for title_i in range(len(first_list)): # 生成目录页 prs = set_content(prs, first_list, title_i, content_font, current_content, other_content, current_rgb, other_rgb, content_number_rgb) # 遍历二级标题生成内容页 for second_title in second_dict[first_list[title_i]]: prs = gen_page(prs, second_title, content_list[second_title], second_title_rgb, text_img_path, content_font, import_font_size, unsort_img_path, list_font_size) return prs # 全局存储Markdown头部信息 header = {} def explain_markdown(file_path): """解析Markdown文件结构 Args: file_path (str): Markdown文件路径 Returns: tuple: (一级标题列表, 二级标题字典, 内容字典, 头部信息字典) """ first_list = [] # 存储一级标题 second_dict = {} # 存储二级标题结构 {一级标题: [二级标题]} content_list = {} # 存储内容结构 {二级标题: [内容项]} txtfile = linecache.getlines(file_path) # 读取文件所有行 line_range = iter(range(len(txtfile))) # 创建行号迭代器 is_code = False # 代码块标记 first_title = "" # 当前一级标题 second_title = "" # 当前二级标题 is_header = False # YAML头部标记 # 逐行解析Markdown for line_index in line_range: line = txtfile[line_index] line_strs = line.split(" ") # 清理行内容(非代码块时) if not is_code: line = line.replace("\r", "").replace("\n", "").replace("\t", "") if len(line) == 0: continue elif line == "---": # YAML头部标记 is_header = not is_header elif is_header: # 解析头部信息 if line_strs[0] == "Title:": header['Title'] = line_strs[1].replace("\n", "") elif line_strs[0] == "Author:": header['Author'] = line_strs[1].replace("\n", "") elif line_strs[0] == "Date:": header['Date'] = line_strs[1].replace("\n", "") elif line_strs[0] == '#': # 一级标题 first_title = line[2:] first_list.append(first_title) second_dict[first_title] = [] elif line_strs[0] == '##': # 二级标题 second_title = line[3:] second_dict[first_title].append(second_title) content_list[second_title] = [] elif line_strs[0] == '-': # 列表项 content_list[second_title].append({ "type": "list", "detail": line[2:] }) elif line[:3] == "```": # 代码块开始/结束 is_code = not is_code elif is_code: # 跳过代码块内容 continue elif line[:2] == '![': # 图片 img_path_str = line.split('(')[-1].split(")")[0] content_list[second_title].append({ "type": "image", "detail": img_path_str }) elif line[:2] == '> ': # 引用文本 content_list[second_title].append({ "type": "text", "detail": line[2:] }) return first_list, second_dict, content_list, header def gen_page(prs, title, content_list, second_title_rgb, text_img_path, content_font, import_font_size, unsort_img_path, list_font_size): """生成内容页幻灯片 Args: prs: PPT对象 title (str): 二级标题 content_list (list): 内容项列表 ...其他样式参数... Returns: Presentation: 更新后的PPT对象 """ # 使用模板的第1个版式创建幻灯片 oneSlide = prs.slides.add_slide(prs.slide_layouts[0]) body_shapes = oneSlide.shapes.placeholders # 设置标题样式 title_shape = body_shapes[0] title_frame = title_shape.text_frame title_paragraph = title_frame.paragraphs[0] run = title_paragraph.add_run() run.text = title font = run.font font.color.rgb = second_title_rgb font.size = Pt(30) # 30磅字号 top_tag = 2 # 初始垂直位置 is_have_img = False # 图片布局标记 # 遍历内容项 for content in content_list: if content['type'] == "text": # 处理文本内容 oneSlide.shapes.add_picture(text_img_path, Inches(0.25), Inches(1.0)) # 添加文本框背景 # 创建文本框 txBox = oneSlide.shapes.add_textbox(Inches(0.35), Inches(1.2), Inches(13), Inches(1)) tf = txBox.text_frame title = tf.paragraphs[0] run = title.add_run() run.text = content['detail'] font = run.font font.name = content_font font.size = Pt(import_font_size) elif content['type'] == "list": # 处理列表项 # 根据内容数量选择布局 if len(content_list) >= 3 and is_have_img: oneSlide.shapes.add_picture(unsort_img_path, Inches(7), Inches(top_tag)) txBox = oneSlide.shapes.add_textbox(Inches(7.5), Inches(top_tag), Inches(5.5), Inches(0.25)) else: oneSlide.shapes.add_picture(unsort_img_path, Inches(1), Inches(top_tag)) txBox = oneSlide.shapes.add_textbox(Inches(1.5), Inches(top_tag), Inches(10), Inches(0.25)) # 设置列表文本 tf = txBox.text_frame title = tf.paragraphs[0] run = title.add_run() run.text = content['detail'] font = run.font font.name = content_font font.size = Pt(list_font_size) font.bold = True top_tag += 0.5 # 下移位置 elif content['type'] == "image": # 处理图片 detail_img_path = content['detail'] # 根据布局状态选择图片位置 if len(content_list) >= 3 and not is_have_img: oneSlide.shapes.add_picture(detail_img_path, Inches(0.3), Inches(top_tag), width=Inches(6)) is_have_img = True elif is_have_img: oneSlide.shapes.add_picture(detail_img_path, Inches(7), Inches(top_tag), width=Inches(6)) else: oneSlide.shapes.add_picture(detail_img_path, Inches(1.5), Inches(top_tag), height=Inches(5)) return prs def gen_ppt_default(model_name, out_file_path, md_file_path, project_path, other_rgb, current_rgb, content_number_rgb, second_title_rgb): """PPT生成主流程 Args: model_name (str): 模板名称 out_file_path (str): 输出文件路径 md_file_path (str): Markdown文件路径 project_path (str): 项目根目录 ...颜色参数... """ # 字体配置 content_font = '微软雅黑' # 全局字体 # 图片路径配置 imgPath = project_path + '/ppt-model/images/' other_content = imgPath + model_name + '/other_content.png' # 普通项图标 current_content = imgPath + model_name + '/current_content.png' # 高亮项图标 text_img_path = imgPath + model_name + '/text_background.png' # 文本框背景 unsort_img_path = imgPath + model_name + '/unsort_list.png' # 列表图标 # 字号配置 list_font_size = 18 # 列表项字号 import_font_size = 16 # 重点文本字号 # 模板文件路径 file_path = project_path + '/ppt-model/' + model_name + '.pptx' end_tittle_content = "感谢各位的聆听\n请领导批评指正" # 结束页固定文本 # 初始化PPT对象 prs = set_presentation(file_path) # 解析Markdown文件 first_list, second_dict, content_list, header = explain_markdown(md_file_path) # 生成首页 prs = set_home(prs, header['Title'], header['Author'], current_rgb) # 标题,作者,当前文字颜色 # 生成内容页 prs = set_for_content(prs, first_list, second_dict, content_list, second_title_rgb, content_font, current_content, other_content, current_rgb, other_rgb, content_number_rgb, text_img_path, import_font_size, unsort_img_path, list_font_size) # 生成结束页 prs = set_end(prs, header['Date'], end_tittle_content) # 保存生成的PPT文件 prs.save(out_file_path)