阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。
你正在解析一份扫描件或工程图纸,肉眼看到表格在页面右侧,但
get_text()
返回的坐标却指向左上角;或者试图用代码给某个段落加高亮标注,结果黄色色块跑到了页眉位置。这通常不是解析库的 Bug,而是 PDF 坐标系与视觉渲染不一致造成的。PDF 规范允许页面携带
/Rotate
属性(0、90、180、270度),阅读器在渲染时会自动应用这个旋转,但底层内容流的坐标依然基于未旋转的原始 MediaBox。如果你直接使用提取到的原始坐标进行绘图或区域匹配,就会发生错位。
理解未旋转空间与显示空间的差异
PyMuPDF 从 v1.17.0 开始明确区分了这两种坐标系。
Page.get_text()
、
Page.get_image_bbox()
以及所有注释对象的坐标,默认都相对于
未旋转页面
返回。这意味着无论 PDF 阅读器把页面转了多少度,这些 API 给出的数值始终锚定在原始的宽高上。只有
Page.rect
和
Page.bound()
会反映旋转后的实际显示尺寸。
这种设计是为了保证内容提取的稳定性:即使你修改了页面旋转角度,同一段文字的提取坐标也不会变。但当你需要将这些坐标“画”回页面,或者与鼠标点击等视觉交互对齐时,就必须手动完成坐标变换。PyMuPDF 提供了两个关键矩阵属性:
Page.rotation_matrix
用于将未旋转坐标转换为显示坐标,
Page.derotation_matrix
则执行逆向操作。这两个矩阵封装了平移和旋转变换,避免了开发者手写三角函数计算。
合成示例:坐标变换的正确与错误预期
为了演示这一机制,我们构造一个纯内存中的合成场景,无需依赖外部文件。若你需要测试真实文档,请提前准备一份包含
/Rotate=90
属性的 PDF 文件,并确保其 CropBox 与 MediaBox 一致,以排除裁切偏移的干扰。
输入设定
:创建一个 A4 纵向页面(595 x 842 pt),设置
/Rotate = 90
。在未旋转空间中,点 (100, 100) 位于左上角附近;但在旋转90度后的显示空间中,由于坐标系随之旋转,该点在视觉上的位置会发生显著变化。
正确输出预期
:将 (100, 100) 乘以
rotation_matrix
后,得到的显示坐标应当落在旋转后页面的有效边界(即
page.rect
)内。对于默认的 A4 页面逆时针旋转90度,原左上角的点在显示空间中应变为左下角附近,即 x 坐标较大而 y 坐标接近 0。
切勿盲目假设 y > x
,具体数值完全取决于 MediaBox 的原点定义。
边界情况处理
:当旋转角度为 0 时,
rotation_matrix
应为单位矩阵。若传入非 90 度倍数的非法旋转值,PyMuPDF 通常会归一化处理,但防御性编程仍建议检查矩阵有效性,避免产生 NaN 坐标。
Python 实现坐标校验逻辑
以下示例展示如何在内存中验证坐标变换。请注意,此代码仅用于演示矩阵运算逻辑,并非完整的 PDF 处理管线。运行前需确保安装了
PyMuPDF
库,并在你自己的锁定依赖环境中执行。
import fitz # PyMuPDF
def validate_rotation_transform():
"""合成示例:验证旋转矩阵在未旋转坐标与显示坐标间的转换"""
doc = fitz.open() # 空文档,无需文件路径
page = doc.new_page(width=595, height=842)
# 设置页面旋转90度,模拟横向显示的PDF
page.set_rotation(90)
# 定义一个在未旋转空间中的测试点 (左上角附近)
unrotated_point = fitz.Point(100, 100)
# 获取旋转矩阵并执行变换
mat = page.rotation_matrix
display_point = unrotated_point * mat
# 验证1: 变换后的点必须位于旋转后的页面可视区域内
assert page.rect.contains(display_point), (
f"变换后坐标 {display_point} 超出显示边界 {page.rect}"
)
# 验证2: 对于默认A4逆时针旋转90度,原(100,100)应变为左下角
# 即 x 接近页宽(842),y 接近 0。注意:此处纠正了常见的 y>x 误区
assert display_point.x > 500 and display_point.y < 200, (
f"90度旋转预期左下角,实际得到 ({display_point.x:.1f}, {display_point.y:.1f})"
)
# 验证3: 逆变换可精确恢复原始坐标
inv_mat = page.derotation_matrix
recovered = display_point * inv_mat
assert abs(recovered.x - unrotated_point.x) < 0.01
assert abs(recovered.y - unrotated_point.y) < 0.01
doc.close()
print("坐标变换逻辑校验通过(合成数据)")
# 请在本地环境运行,不要假设线上环境已安装fitz
validate_rotation_transform()
这段代码没有读取任何真实文件,完全在内存中构造页面状态。
assert
语句检查的是数学关系的合理性,而非业务正确性。如果你的实际 PDF 包含非标准 MediaBox 或嵌套变换,可能需要额外调试。
验收标准与人工审查边界
代码能通过断言不代表生产环境万无一失。下表列出常见异常及其处理决策,帮助区分自动化可解决与需人工介入的场景。
观察现象
可能原因
处理决定
标注整体偏移固定距离
CropBox 与 MediaBox 原点不一致
使用
page.cropbox_position
补偿偏移量
旋转后文字坐标正常但图片错位
图片使用了独立 CTM 变换矩阵
改用
get_image_rects()
而非
get_image_bbox()
部分页面旋转正确,部分错乱
文档混合了不同旋转角度的页面
逐页检查
page.rotation
,禁用全局缓存矩阵
变换后坐标超出页面边界
原始内容本身被裁切或存在负坐标
标记为异常页,转人工审查
0度页面无需变换但仍有偏差
第三方工具写入的隐藏旋转元数据
强制调用
remove_rotation()
重置后再处理
注意:本表针对坐标变换逻辑,不涵盖 OCR 精度或字体编码问题。当遇到复杂排版(如多栏混排+局部旋转图表)时,自动校验可能失效,此时应暂停流水线,由人工判断是否继续。
参考资料与下一步
PyMuPDF Page.rotation_matrix 官方文档
PyMuPDF Coordinates 概念说明
将这份检查清单用于文档翻译
先把页面顺序、来源位置与文本完整性检查清楚,再把同一份脱敏样本交给翻译流程。译后仍要检查标题、正文和图注是否正确对应,不能只看文字是否通顺。
OmniDoc(产品入口)
提供多格式文档翻译入口,官网介绍了 OCR、术语表与双语对照功能。对希望直接从文件开始处理的团队,这是一条值得用实际样本评估的路径。请使用有权上传的脱敏文件,并用本篇的检查项复核结果;这里不声称 OmniDoc 使用了本文的开源库或内部算法,也不以示例代替产品性能测试。
- 1 -
更新于 2026-10-08