You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyMuPDF page.insert_textbox函数执行异常问题求助

问题诊断与解决方案

你遇到的insert_textbox偶尔失效、多次运行结果不一致的问题,核心原因集中在字体加载稳定性、页面状态同步、文本适配逻辑这几个方面,以下是具体分析和修复方案:


问题根源

  1. 重复加载字体引发资源冲突:每次调用insert_textbox时重复加载字体文件,可能导致PyMuPDF内部资源加载异常,随机出现文本插入失败。
  2. 页面状态未同步:执行apply_redactions后,页面内部结构未及时刷新,直接插入文本会出现坐标偏移或写入失败。
  3. 未处理文本溢出情况:翻译后的文本长度可能超过原文本块的矩形范围,若未做适配,会出现文本截断或完全无法插入的情况。
  4. 翻译与插入时序冲突:插入阶段才调用翻译接口,耗时的翻译操作可能导致页面上下文状态异常。

修复后的完整代码

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)

关键优化点

  1. 全局字体加载:在文档初始化时一次性加载字体,后续插入文本复用字体ID,避免重复加载引发的资源冲突。
  2. 提前批量翻译:先完成所有文本的翻译操作,再执行页面修改,避免翻译耗时导致页面状态异常。
  3. 动态字体适配:计算文本所需高度,自动缩小字体确保文本能完整放入原文本块区域,解决文本溢出问题。
  4. 页面状态同步:执行apply_redactions后调用page.clean_contents(),确保页面内部结构刷新完成。
  5. 插入结果校验:通过insert_textbox的返回值检查未插入字符数,方便调试异常情况。

内容的提问来源于stack exchange,提问作者ihnfsa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 19:48:15