# ========================================== # 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: pass if original_font['color']: try: if hasattr(original_font['color'], 'rgb'): run.font.color.rgb = original_font['color'].rgb except: pass def replace_picture(prs, slide_index, placeholder, 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)