如何获取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()
解决方案
核心问题分析
- 坐标基准不匹配:pdfminer返回的LTChar.bbox是字符的包围盒(原点在页面左下角,左上为x0,y1,右下为x1,y0),但PyMuPDF的
insert_text方法接收的是文本基线的左下角坐标,直接用包围盒坐标会导致位置偏移。 - 字体硬编码问题:原代码固定使用
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
相关产品推荐
相关产品推荐

