162 lines
6.4 KiB
Python
162 lines
6.4 KiB
Python
# ==========================================
|
|
# 3. PPT 通用工具 (PPT Utilities)
|
|
# ==========================================
|
|
import os
|
|
|
|
from loguru import logger
|
|
|
|
from utils.font_utils import is_font_available
|
|
from utils.image_utils import get_corrected_image_stream
|
|
|
|
|
|
def replace_text_in_slide(prs, slide_index, placeholder, text):
|
|
"""在指定幻灯片中替换指定占位符的文本,并保持原有格式"""
|
|
slide = prs.slides[slide_index]
|
|
for shape in slide.shapes:
|
|
if shape.name == placeholder:
|
|
if not shape.has_text_frame:
|
|
continue
|
|
|
|
# 1. 保存原有格式
|
|
original_paragraph_formats = []
|
|
for paragraph in shape.text_frame.paragraphs:
|
|
paragraph_format = {
|
|
"alignment": paragraph.alignment,
|
|
"space_before": getattr(paragraph, "space_before", None),
|
|
"space_after": getattr(paragraph, "space_after", None),
|
|
"line_spacing": getattr(paragraph, "line_spacing", None),
|
|
"left_indent": getattr(paragraph, "left_indent", None),
|
|
"right_indent": getattr(paragraph, "right_indent", None),
|
|
"first_line_indent": getattr(paragraph, "first_line_indent", None),
|
|
"font_info": [],
|
|
}
|
|
for run in paragraph.runs:
|
|
run_format = {
|
|
"font_name": run.font.name,
|
|
"font_size": run.font.size,
|
|
"bold": run.font.bold,
|
|
"italic": run.font.italic,
|
|
"underline": run.font.underline,
|
|
"color": run.font.color,
|
|
"character_space": getattr(run.font, "space", None),
|
|
"all_caps": getattr(run.font, "all_caps", None),
|
|
"small_caps": getattr(run.font, "small_caps", None),
|
|
}
|
|
paragraph_format["font_info"].append(run_format)
|
|
original_paragraph_formats.append(paragraph_format)
|
|
|
|
# 2. 设置新文本
|
|
shape.text = str(text) # 确保是字符串
|
|
|
|
# 3. 恢复格式
|
|
for i, paragraph in enumerate(shape.text_frame.paragraphs):
|
|
orig_idx = i if i < len(original_paragraph_formats) else -1
|
|
if not original_paragraph_formats:
|
|
break
|
|
|
|
original_para = original_paragraph_formats[orig_idx]
|
|
|
|
# 恢复段落属性
|
|
for attr in [
|
|
"alignment",
|
|
"space_before",
|
|
"space_after",
|
|
"line_spacing",
|
|
"left_indent",
|
|
"right_indent",
|
|
"first_line_indent",
|
|
]:
|
|
if original_para[attr] is not None:
|
|
setattr(paragraph, attr, original_para[attr])
|
|
|
|
# 恢复字体属性
|
|
for j, run in enumerate(paragraph.runs):
|
|
font_idx = j if j < len(original_para["font_info"]) else 0
|
|
if not original_para["font_info"]:
|
|
break
|
|
|
|
original_font = original_para["font_info"][font_idx]
|
|
|
|
# 字体名称检查与回退
|
|
if original_font["font_name"]:
|
|
if is_font_available(original_font["font_name"]):
|
|
run.font.name = original_font["font_name"]
|
|
else:
|
|
run.font.name = "微软雅黑"
|
|
|
|
# 恢复其他字体属性
|
|
if original_font["font_size"]:
|
|
run.font.size = original_font["font_size"]
|
|
if original_font["bold"]:
|
|
run.font.bold = original_font["bold"]
|
|
if original_font["italic"]:
|
|
run.font.italic = original_font["italic"]
|
|
if original_font["underline"]:
|
|
run.font.underline = original_font["underline"]
|
|
if original_font["all_caps"]:
|
|
run.font.all_caps = original_font["all_caps"]
|
|
|
|
if original_font["character_space"]:
|
|
try:
|
|
run.font.space = original_font["character_space"]
|
|
except:
|
|
logger.error(f"错误: 无法设置字体间距 {original_font['character_space']}")
|
|
|
|
if original_font["color"]:
|
|
try:
|
|
if hasattr(original_font["color"], "rgb"):
|
|
run.font.color.rgb = original_font["color"].rgb
|
|
except:
|
|
logger.error(f"错误: 无法设置字体颜色 {original_font['color']}")
|
|
|
|
|
|
def replace_picture(prs, slide_index, placeholder, img_path):
|
|
"""
|
|
在指定幻灯片中替换指定占位符的图片(包含自动旋转修复)
|
|
|
|
参数:
|
|
prs: Presentation 对象
|
|
slide_index: 幻灯片索引 (从0开始)
|
|
placeholder: 占位符名称 (例如 "signature")
|
|
img_path: 图片路径
|
|
"""
|
|
if not os.path.exists(img_path):
|
|
logger.warning(f"警告: 图片路径不存在 {img_path}")
|
|
return
|
|
|
|
slide = prs.slides[slide_index]
|
|
sp_tree = slide.shapes._spTree
|
|
|
|
target_shape = None
|
|
target_index = -1
|
|
|
|
# 1. 先找到目标形状和它的索引
|
|
for i, shape in enumerate(slide.shapes):
|
|
if shape.name == placeholder:
|
|
target_shape = shape
|
|
target_index = i
|
|
break
|
|
|
|
if target_shape:
|
|
# 获取原位置信息
|
|
left, top, width, height = (
|
|
target_shape.left,
|
|
target_shape.top,
|
|
target_shape.width,
|
|
target_shape.height,
|
|
)
|
|
|
|
# 2. 获取修正后的图片流
|
|
img_stream = get_corrected_image_stream(img_path)
|
|
|
|
# 3. 移除旧形状
|
|
sp_tree.remove(target_shape._element)
|
|
|
|
# 4. 插入新图片 (使用流而不是路径)
|
|
new_shape = slide.shapes.add_picture(img_stream, left, top, width, height)
|
|
|
|
# 5. 恢复层级位置 (z-order)
|
|
sp_tree.insert(target_index, new_shape._element)
|
|
else:
|
|
logger.warning(f"警告: 幻灯片 {slide_index} 中未找到占位符 {placeholder}")
|