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

Python转换PDF中AutoCAD SHX文本为可搜索文本的坐标定位问题

问题背景

包含AutoCAD SHX Text的PDF无法直接进行文本搜索,需求是将PDF内所有AutoCAD SHX Text转换为可检索的普通文本,实现PDF全文搜索功能。参考现有注释转文本的实现方案落地时,存在两个核心问题:

  • 无法获取文本精准位置,转换后的文本不能和原注释位置完全重合
  • 生成的文本相对原文本偏转90度

目前已经实现读取PDF全页面内容、合并生成新PDF的基础功能,现有实现代码如下:

import io,os
import PyPDF2
from PyPDF2 import PdfFileWriter, PdfFileReader
from reportlab.pdfgen import canvas
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, landscape
from datetime import date, time, datetime,timedelta

def convert_annot(pdf_name):
    pdf = PdfFileReader(open(pdf_name, "rb"))
    numPages = pdf.getNumPages()
    output = PdfFileWriter()

    # 遍历所有页面
    for i in range(0, numPages):
        page = pdf.getPage(i) 
        objs=[]
        try :
            for annot in page['/Annots']:
                obj=annot.getObject()
                if 'AutoCAD SHX Text' in obj.values():
                    print (obj['/Contents'],obj['/Rect'])
                    objs.append(obj)
        
            page_size=pdf.getPage(0).mediaBox.upperRight
            w, h =page_size
            packet = io.BytesIO()
            c = canvas.Canvas(packet,pagesize=landscape(A4))
            c.setFont('Helvetica-Bold',12)
            c.setFillColor(colors.red)
            for obj in objs:
                if '/Contents' in obj:
                    xy=tuple(obj['/Rect'])
                    llx,lly,urx,ury=xy   # 左下角X、左下角Y、右上角X、右上角Y
                    text=obj['/Contents']
                    x1=int(urx)
                    y1=int(lly)
                    c.drawString(x1,y1,text)
            c.save()

            # 缓冲区指针重置到起始位置
            packet.seek(0)
            new_pdf = PdfFileReader(packet)
            page=None
            new_pdf_file_name=None

            # 读取原有PDF页面合并新生成的文本层
            page = pdf.pages[i]
            page.merge_page(new_pdf.pages[0])
            output.add_page(page)
        except :
            page_size=pdf.getPage(0).mediaBox.upperRight
            w, h =page_size
            packet = io.BytesIO()
            c = canvas.Canvas(packet,pagesize=landscape(A4))
            c.setFont('Helvetica-Bold',12)
            c.setFillColor(colors.red)
            for obj in objs:
                if '/Contents' in obj:
                    xy=tuple(obj['/Rect'])
                    llx,lly,urx,ury=xy
                    text=obj['/Contents']
                    x1=int(urx)
                    y1=int(lly)
                    c.drawString(x1,y1,text)
            c.save()
            packet.seek(0)
            new_pdf = PdfFileReader(packet)
            page=None
            new_pdf_file_name=None
            page = pdf.pages[i]
            page.merge_page(new_pdf.pages[0])
            output.add_page(page)

    # 输出最终PDF
    new_pdf_file_name=os.path.splitext(pdf_name)[0]+".annot.pdf"
    outputStream = open(new_pdf_file_name, "wb")
    output.write(outputStream)
    outputStream.close()

if __name__ == '__main__':
    pdf_name='samplePdf.pdf'
    convert_annot(pdf_name)

效果对比

  • 原始样例PDF:带不可选中、不可搜索的AutoCAD SHX Text注释
    样例PDF效果
  • 当前代码输出效果:红色的NOTES文本没有和原黑色AutoCAD SHX Text的NOTES位置完全重合
    当前输出效果
  • 预期实现效果:转换后的可搜索文本和原注释位置完全对齐
    预期效果
解决方案

现有代码的两个问题不需要额外引入pdfminer就能解决,核心是三个逻辑错误:

  1. 页面尺寸硬编码为landscape(A4),没有和原PDF页尺寸保持一致,这是位置偏移的核心原因
  2. 坐标取值错误:现有逻辑取注释矩形的右上角X、左下角Y作为文本绘制起点,正常文本绘制起点应该是矩形的左下角坐标
  3. 没有读取注释自带的旋转参数:AutoCAD导出的SHX文本注释会把旋转角度存在/Rotation字段里,直接绘制自然会出现90度偏转
    另外现有代码中try和except块的绘制逻辑完全重复,属于无效冗余,可以直接删除重复分支,只需要在访问annot字段前加存在性判断即可。

修正步骤

  • 第一步:删除硬编码的A4页面尺寸,创建reportlab画布时直接使用原页面的实际宽高,保证坐标系完全对齐
  • 第二步:修正文本绘制坐标,统一取注释矩形的左下角(llx, lly)作为绘制基准点,不要用右上角urx坐标
  • 第三步:读取每个注释对象的/Rotation参数,绘制文本前调用canvas的状态保存、平移、旋转方法做对应角度变换,绘制完成后恢复画布状态避免影响其他文本
  • 第四步:不要固定写死12号字体,通过注释矩形的高度(ury - lly)动态计算适配的字体大小,避免文本大小和原注释不匹配
  • 第五步:正式使用时把文本填充色透明度设为0,绘制的文本会完全透明,不影响原PDF显示效果,同时支持文本搜索选中,和原生可搜索PDF体验完全一致

修正后核心代码片段

替换原来遍历objs绘制文本的部分即可:

# 直接取当前页的实际尺寸,不要硬编码A4,也不要固定取第0页尺寸
page_size = page.mediabox.upperRight
w, h = float(page_size[0]), float(page_size[1])
packet = io.BytesIO()
c = canvas.Canvas(packet, pagesize=(w, h))

for obj in objs:
    if '/Contents' not in obj:
        continue
    llx, lly, urx, ury = [float(v) for v in obj['/Rect']]
    text = obj['/Contents']
    # 读取旋转角度,默认0度
    rotation = int(obj.get('/Rotation', 0))
    # 计算适配的字体大小,取矩形高度的0.8倍避免溢出
    font_size = (ury - lly) * 0.8
    c.setFont('Helvetica-Bold', font_size)
    # 测试阶段用半透红色,正式使用把alpha改成0实现隐形文本
    c.setFillColorRGB(1, 0, 0, alpha=0.8)
    
    # 保存画布状态,旋转后恢复避免影响其他文本绘制
    c.saveState()
    # 平移到文本绘制起点
    c.translate(llx, lly)
    # 按注释角度旋转,PDF坐标系y轴向上,旋转方向和常规图像坐标系相反
    c.rotate(rotation)
    # 已经平移到基准点,坐标填(0,0)即可
    c.drawString(0, 0, text)
    c.restoreState()

如果确实需要用pdfminer做二次坐标校验,可以按页面维度提前提取当前页所有文本块的坐标和内容,和注释的/Contents做文本匹配,取重叠度最高的文本块坐标作为绘制基准即可,不需要在遍历注释的循环里重复初始化pdfminer解析器,单页只需要解析一次。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 09:48:27