# ========================================== # 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}")