You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

406 lines
15 KiB

5 months ago
"""
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 # 下移位置
5 months ago
# 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))
5 months ago
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)