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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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