|
|
"""
|
|
|
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) |