PyMuPDF page.insert_textbox函数执行异常问题求助
问题诊断与解决方案
你遇到的insert_textbox偶尔失效、多次运行结果不一致的问题,核心原因集中在字体加载稳定性、页面状态同步、文本适配逻辑这几个方面,以下是具体分析和修复方案:
问题根源
- 重复加载字体引发资源冲突:每次调用
insert_textbox时重复加载字体文件,可能导致PyMuPDF内部资源加载异常,随机出现文本插入失败。 - 页面状态未同步:执行
apply_redactions后,页面内部结构未及时刷新,直接插入文本会出现坐标偏移或写入失败。 - 未处理文本溢出情况:翻译后的文本长度可能超过原文本块的矩形范围,若未做适配,会出现文本截断或完全无法插入的情况。
- 翻译与插入时序冲突:插入阶段才调用翻译接口,耗时的翻译操作可能导致页面上下文状态异常。
修复后的完整代码
import os import fitz from translator import Translator def dec_to_rgb_float(dec): hex_val = format(dec, "06x") r = int(hex_val[0:2], 16) g = int(hex_val[2:4], 16) b = int(hex_val[4:6], 16) return round(r / 255, 4), round(g / 255, 4), round(b / 255, 4) class PDFHandler: def __init__(self, translator: Translator): self.translator = translator def handle(self, input_file: str, output_file=None): if not os.path.exists(input_file): raise FileNotFoundError("File does not exist: {}".format(input_file)) self.input_file = input_file self.output_file = output_file if self.output_file is None: self.output_file = os.path.join( os.path.dirname(input_file), "translated_" + os.path.basename(input_file), ) self.__handle() def __handle(self): doc = fitz.open(self.input_file) # 提前全局加载字体,避免重复加载引发异常 font_id = doc.add_font(fontname="MiSans", fontfile="./fonts/MiSans-Regular.ttf") for i, page in enumerate(doc): print(f"Processing page {i+1}: ", end="") blks = page.get_text("blocks") # 过滤非文本块 blks = [blk for blk in blks if blk[6] != 1] new_blks = [] # 第一步:批量完成所有文本翻译,避免插入时耗时操作影响页面状态 for j, blk in enumerate(blks): style = page.get_text("dict", flags=11)["blocks"][blk[5]]["lines"][0]["spans"][0] original_text = blk[4].strip() translated_text = self.translator.translate(original_text) new_blks.append({ "fontsize": style["size"], "rect": blk[:4], "color": dec_to_rgb_float(style["color"]), "text": translated_text }) print(f"{j+1}/{len(blks)}", end=" ", flush=True) print("") # 第二步:执行红act删除原文本块 for new_blk in new_blks: page.add_redact_annot(new_blk["rect"]) page.apply_redactions() # 刷新页面内容,确保红act操作完全生效 page.clean_contents() # 第三步:插入翻译后的文本,增加文本适配逻辑 for j, new_blk in enumerate(new_blks): rect = new_blk["rect"] fontsize = new_blk["fontsize"] text = new_blk["text"] color = new_blk["color"] # 动态调整字体大小,确保文本能完整放入原矩形区域 # 计算文本所需高度(按原宽度换行) line_count = fitz.get_text_length(text, fontsize=fontsize, fontname="MiSans") / rect.width text_height = line_count * fontsize * 1.2 # 1.2为行间距系数 # 若文本高度超出矩形范围,逐步缩小字体 while text_height > rect.height and fontsize > 6: # 最小字体限制为6号 fontsize -= 0.5 line_count = fitz.get_text_length(text, fontsize=fontsize, fontname="MiSans") / rect.width text_height = line_count * fontsize * 1.2 # 插入文本,使用提前加载的字体ID uninserted_len = page.insert_textbox( rect, buffer=text, fontsize=fontsize, color=color, fontid=font_id, align=fitz.TEXT_ALIGN_LEFT ) # 打印未插入的字符数,用于调试 if uninserted_len > 0: print(f"Warning: Page {i+1}, Block {j+1} has {uninserted_len} characters not inserted.") print(f"Page {i+1} {'is wrapped' if page.is_wrapped else 'is not wrapped'}\n") # 处理输出文件已存在的情况 while os.path.exists(self.output_file): print("The output file already exists.") self.output_file = input("New path: ").strip() if len(self.output_file) == 0: print("Ignore output.") return # 保存文档时清理无效资源 doc.save(self.output_file, garbage=3, deflate=True, clean=True)
关键优化点
- 全局字体加载:在文档初始化时一次性加载字体,后续插入文本复用字体ID,避免重复加载引发的资源冲突。
- 提前批量翻译:先完成所有文本的翻译操作,再执行页面修改,避免翻译耗时导致页面状态异常。
- 动态字体适配:计算文本所需高度,自动缩小字体确保文本能完整放入原文本块区域,解决文本溢出问题。
- 页面状态同步:执行
apply_redactions后调用page.clean_contents(),确保页面内部结构刷新完成。 - 插入结果校验:通过
insert_textbox的返回值检查未插入字符数,方便调试异常情况。
内容的提问来源于stack exchange,提问作者ihnfsa
相关产品推荐
相关产品推荐

