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

PDF全组件复刻及翻译后样式缺失问题修复求助

问题修复:PDF翻译重建时缺失样式(背景色、表格网格线)

问题概述

提取PDF的页面布局、文本样式、表格、图片等所有组件,基于这些信息重建带翻译功能的新PDF,但生成的文档缺失文本背景色、表格网格线及单元格底纹。当前使用PyMuPDF(fitz)提取文本,ReportLab重建PDF。

问题根源

  1. 原代码仅通过注释(annotation)提取背景色,但很多PDF的文本背景、单元格底纹是通过**图形路径(Path)**实现的,并非注释
  2. 完全未处理表格的网格线,这类线条属于PDF中的图形元素,需要单独提取并绘制

修复后的完整代码

import os
import fitz
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter, landscape, portrait
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from deep_translator import GoogleTranslator

def get_page_orientation(pdf_path):
    doc = fitz.open(pdf_path)
    first_page = doc[0]
    width, height = first_page.rect.width, first_page.rect.height
    return width, height, "Landscape" if width > height else "Portrait"

def convert_color(int_color):
    """Convert PyMuPDF integer color to RGB format (0-1 range)"""
    r = (int_color >> 16) & 255
    g = (int_color >> 8) & 255
    b = int_color & 255
    return r / 255, g / 255, b / 255

def convert_fitz_color(fitz_color):
    """Convert PyMuPDF float color tuple (0-1) to ReportLab RGB format"""
    return fitz_color[0], fitz_color[1], fitz_color[2]

def register_font(font_name):
    font_paths = {
        "ArialMT": "Arial.ttf",
        "TimesNewRomanPSMT": "TimesNewRoman.ttf",
        "Helvetica": "Helvetica.ttf",
    }

    if font_name not in pdfmetrics.getRegisteredFontNames():
        font_path = font_paths.get(font_name)
        if font_path and os.path.exists(font_path):
            pdfmetrics.registerFont(TTFont(font_name, font_path))
        else:
            font_name = "Helvetica"

    return font_name

def auto_adjust_font(canvas_obj, text, x, y, width_limit, font_name="Helvetica", max_font_size=12):
    text = text or ""
    font_size = max_font_size
    while font_size > 6 and canvas_obj.stringWidth(text, font_name, font_size) > width_limit:
        font_size -= 1
    canvas_obj.setFont(font_name, font_size)
    canvas_obj.drawString(x, y, text)

def extract_text_and_colors(pdf_path):
    doc = fitz.open(pdf_path)
    extracted_data = []

    for page in doc:
        blocks = page.get_text("dict")["blocks"]
        # 提取所有文本块的背景色(包括图形路径背景)
        page_bg_regions = []
        # 获取页面中的图形路径
        paths = page.get_drawings()
        for path in paths:
            # 处理填充的矩形(单元格背景、文本背景)
            if path["fill"] != (1,1,1) and len(path["rects"]) > 0:
                for rect in path["rects"]:
                    page_bg_regions.append((fitz.Rect(rect), path["fill"]))

        page_data = []
        for block in blocks:
            for line in block.get("lines", []):
                for span in line["spans"]:
                    span_bbox = fitz.Rect(span["bbox"])
                    # 匹配当前文本块对应的背景色
                    bg_color = (1,1,1)
                    for rect, color in page_bg_regions:
                        if span_bbox.intersects(rect):
                            bg_color = color
                            break
                    page_data.append(
                        (
                            span["bbox"],
                            span["text"],
                            span["font"],
                            span["size"],
                            convert_color(span.get("color", 0)),
                            bg_color
                        )
                    )
        extracted_data.append(page_data)
    return extracted_data

def extract_page_graphics(pdf_path):
    """提取页面中的表格网格线和单元格背景图形"""
    doc = fitz.open(pdf_path)
    graphics_data = []
    for page in doc:
        paths = page.get_drawings()
        page_graphics = {
            "cell_fills": [],  # (bbox, color)
            "grid_lines": []   # (bbox, color, line_width)
        }
        for path in paths:
            # 处理单元格填充(有填充色的矩形)
            if path["fill"] != (1,1,1):
                for rect in path["rects"]:
                    page_graphics["cell_fills"].append((rect, path["fill"]))
            # 处理网格线(无填充的线条/矩形边框)
            if path["fill"] == (1,1,1) and path["stroke"] != (1,1,1):
                # 处理矩形边框
                for rect in path["rects"]:
                    x0, y0, x1, y1 = rect
                    # 上边框
                    page_graphics["grid_lines"].append(((x0, y1, x1, y1), path["stroke"], path["width"]))
                    # 下边框
                    page_graphics["grid_lines"].append(((x0, y0, x1, y0), path["stroke"], path["width"]))
                    # 左边框
                    page_graphics["grid_lines"].append(((x0, y0, x0, y1), path["stroke"], path["width"]))
                    # 右边框
                    page_graphics["grid_lines"].append(((x1, y0, x1, y1), path["stroke"], path["width"]))
                # 处理单独的线条
                for line in path["lines"]:
                    page_graphics["grid_lines"].append((line, path["stroke"], path["width"]))
        graphics_data.append(page_graphics)
    return graphics_data

def translate_text(text, target_language="fr"):
    if not text or not text.strip():
        return ""
    try:
        translator = GoogleTranslator(source="auto", target=target_language)
        return translator.translate(text) or text
    except Exception as e:
        print(f"Translation error: {e}")
        return text

def create_translated_pdf(input_pdf, output_pdf, target_language="fr"):
    text_data = extract_text_and_colors(input_pdf)
    graphics_data = extract_page_graphics(input_pdf)
    width, height, orientation = get_page_orientation(input_pdf)
    page_size = landscape(letter) if orientation == "Landscape" else portrait(letter)
    pdf_canvas = canvas.Canvas(output_pdf, pagesize=page_size)

    for page_idx, page_data in enumerate(text_data):
        page_graphics = graphics_data[page_idx]
        # 1. 先绘制单元格填充背景
        for rect, color in page_graphics["cell_fills"]:
            x0, y0, x1, y1 = rect
            y0_flip = height - y1
            y1_flip = height - y0
            pdf_canvas.saveState()
            pdf_canvas.setFillColorRGB(*convert_fitz_color(color))
            pdf_canvas.rect(x0, y0_flip, x1 - x0, y1_flip - y0_flip, fill=1, stroke=0)
            pdf_canvas.restoreState()
        # 2. 绘制表格网格线
        for line_coords, color, line_width in page_graphics["grid_lines"]:
            x0, y0, x1, y1 = line_coords
            y0_flip = height - y0
            y1_flip = height - y1
            pdf_canvas.saveState()
            pdf_canvas.setStrokeColorRGB(*convert_fitz_color(color))
            pdf_canvas.setLineWidth(line_width)
            pdf_canvas.line(x0, y0_flip, x1, y1_flip)
            pdf_canvas.restoreState()
        # 3. 绘制翻译后的文本
        elements = filter(lambda el: el[1].strip(), page_data)
        for bbox, text, font_name, font_size, text_color, bg_color in elements:
            font_name = register_font(font_name)
            translated_text = translate_text(text, target_language)
            x, y, x1, y1 = bbox
            y_flip = height - y1
            # 绘制文本背景(如果有)
            if bg_color != (1,1,1):
                pdf_canvas.saveState()
                pdf_canvas.setFillColorRGB(*convert_fitz_color(bg_color))
                pdf_canvas.rect(x, y_flip, x1 - x, abs(y1 - y), fill=1, stroke=0)
                pdf_canvas.restoreState()
            # 绘制翻译文本
            pdf_canvas.setFillColorRGB(*text_color)
            auto_adjust_font(pdf_canvas, translated_text, x, y_flip, x1 - x, font_name, font_size)
        pdf_canvas.showPage()
    pdf_canvas.save()
    print(f"Translated PDF saved as: {output_pdf}")

def translate_pdf(input_pdf, output_pdf, target_language="fr"):
    create_translated_pdf(input_pdf, output_pdf, target_language)

# 示例调用(替换为你的文件路径)
# translate_pdf("input.pdf", "output_translated.pdf", target_language="fr")

关键修复说明

  1. 新增图形提取函数:extract_page_graphics 提取PDF中的路径元素,区分单元格填充背景和表格网格线
  2. 完善背景色提取:extract_text_and_colors 现在会匹配图形路径中的背景色,不再仅依赖注释
  3. 分层绘制逻辑:生成PDF时按「单元格背景 → 网格线 → 文本」的顺序绘制,确保层级正确
  4. 坐标转换优化:统一处理PyMuPDF和ReportLab的坐标系差异(Y轴方向相反)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:42:04