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

如何获取PDF字符的位置坐标而非bbox?附代码求助

问题

我编写了一段代码,用于从PDF中提取字符特征并复制到另一个PDF中,但出现了位置不一致的问题。原因是当前获取的是字符的bounding box(bbox)位置而非字符本身的位置,请问如何获取字符的真实位置及字符信息?

原代码

from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer,LTChar,LTImage,LTRect,LTLine,LTTextBox,LTFigure,LTTextLine
import fitz

text_elements= []
for page_layout in extract_pages("helve3.pdf"):
    for element in page_layout:
        if isinstance(element, LTTextBox):
            for text_line in element:
                for character in text_line:
                    if isinstance(character, LTChar):
                        print(character)
                        propiedades_texto={
                            "text":character._text,
                            "fontname": character.fontname,
                            "fontsize": character.size,
                            "x0": character.x0,
                            "y0": character.y0,
                            "x1": character.x1,
                            "y1": character.y1,
                            "matrix":character.matrix,
                            'bbbox':character.bbox
                                    }
                        text_elements.append(propiedades_texto)

        if isinstance(element, LTRect):
            print(element)
            
        if isinstance(element, LTFigure):
            print(element)
           

        if isinstance(element, LTLine):
            print(element)  
           

        if isinstance(element, LTImage):
            print(element)


print("--------------------------------------------------------")
pdf_path = "helve3.pdf"
pdf_document = fitz.open(pdf_path)
page = pdf_document.load_page(0)
width = page.rect.width
height = page.rect.height
pdf_document.close

pdfNew = fitz.open()
pageNew = pdfNew.new_page(width=width, height=height) 

for text_element in text_elements:
    print(text_element)
    matrix_tuple = text_element['matrix'][-1]
    print(matrix_tuple)
    pageNew.insert_text((text_element['x0'],text_element['y1']),text_element['text'],fontsize=text_element["fontsize"],color=(0,0,0),fontname="helv")  # Inserta el texto en la posición (100, 100)
   
   
pdfNew.save("prueba.pdf")
pdfNew.close()
解决方案

核心问题分析

  1. 坐标基准不匹配:pdfminer返回的LTChar.bbox是字符的包围盒(原点在页面左下角,左上为x0,y1,右下为x1,y0),但PyMuPDF的insert_text方法接收的是文本基线的左下角坐标,直接用包围盒坐标会导致位置偏移。
  2. 字体硬编码问题:原代码固定使用fontname="helv",但提取的字体可能带样式(如Helvetica-Bold),字体不匹配会进一步导致字符宽度、位置偏差。

获取字符真实位置与修正代码

1. 计算字符基线位置

字符的基线是文本排版的基准线,可通过两种方式获取:

  • 利用LTChar的变换矩阵:matrix最后两个值是字符的精确平移坐标(x,y),对应基线位置。
  • 包围盒近似计算:多数字体的基线位于包围盒底部(y0)向上约80%的位置。

2. 字体名称映射

将pdfminer提取的字体名转换为PyMuPDF支持的标准名称,示例映射表如下:

  • Helvetica → helv
  • Helvetica-Bold → helvB
  • Helvetica-Italic → helvI
  • Helvetica-BoldItalic → helvBI

修改后的代码

from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextBox, LTChar
import fitz

# 字体名称映射表,可按需扩展
FONT_MAP = {
    "Helvetica": "helv",
    "Helvetica-Bold": "helvB",
    "Helvetica-Italic": "helvI",
    "Helvetica-BoldItalic": "helvBI",
}

text_elements = []
for page_layout in extract_pages("helve3.pdf"):
    for element in page_layout:
        if isinstance(element, LTTextBox):
            for text_line in element:
                for character in text_line:
                    if isinstance(character, LTChar):
                        # 从变换矩阵提取字符精确基线位置
                        # matrix格式:(scale_x, shear_x, shear_y, scale_y, trans_x, trans_y)
                        trans_x, trans_y = character.matrix[-2], character.matrix[-1]
                        # 也可使用包围盒近似计算基线:baseline_y = character.y0 + (character.y1 - character.y0)*0.8
                        
                        propiedades_texto = {
                            "text": character._text,
                            "fontname": character.fontname,
                            "fontsize": character.size,
                            "baseline_x": trans_x,
                            "baseline_y": trans_y,
                            "bbox": character.bbox
                        }
                        text_elements.append(propiedades_texto)

# 创建新PDF
pdf_document = fitz.open("helve3.pdf")
page = pdf_document.load_page(0)
width, height = page.rect.width, page.rect.height
pdf_document.close()

pdfNew = fitz.open()
pageNew = pdfNew.new_page(width=width, height=height)

for text_element in text_elements:
    # 匹配对应字体,默认用helv兜底
    font_name = FONT_MAP.get(text_element["fontname"], "helv")
    # 使用基线位置插入文本
    pageNew.insert_text(
        (text_element["baseline_x"], text_element["baseline_y"]),
        text_element["text"],
        fontsize=text_element["fontsize"],
        color=(0, 0, 0),
        fontname=font_name
    )

pdfNew.save("prueba.pdf")
pdfNew.close()

额外优化建议

如果需要更精确的基线位置,可以通过pdfminer获取字体的ascent(上升高度)和descent(下降高度),计算公式为:baseline_y = character.y0 + character.size * (character.ascent / 1000)(部分字体的ascent值以千分比表示)。
对于带旋转、缩放的文本,需要完整应用LTChar的matrix变换到坐标上,确保位置完全匹配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 18:58:12