pypdf Transformation 类全解析:用 2D 变换矩阵实现 PDF 页面的平移、缩放与旋转
2026/9/15 13:58:59 网站建设 项目流程

pypdf Transformation 类全解析:用 2D 变换矩阵实现 PDF 页面的平移、缩放与旋转

【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf

本指南以 pypdf 的 Transformation 类(API 文档见 docs/modules/Transformation.rst)为核心,深入讲解 PDF 页面 2D 变换矩阵的数学原理、Transformation对象的各种构造与操作方法,以及如何通过PageObject.add_transformation()将变换应用到真实 PDF 页面。读完本文,你将能够用 pypdf 精确控制页面内容的缩放、平移、旋转与镜像,并能理解底层cm操作符与矩阵乘法的实现细节。

PDF 中的 2D 变换矩阵:从数学到六元组

在 PDF 规范中,两个坐标系之间的变换由一个 3×3 变换矩阵表示,其形式固定为:

a b 0 c d 0 e f 1

坐标变换通过矩阵乘法表达:

a b 0 [ x′ y′ 1 ] = [ x y 1 ] × c d 0 e f 1

因为矩阵的第三列固定为0 0 1,真正可变只有 6 个元素,所以 PDF 中通常用一个六元素数组[ a b c d e f ]来描述该矩阵。在 pypdf 中,这个六元组被称为压缩变换矩阵(Compressed Transformation Matrix),对应的类型别名定义于 types.py(CompressedTransformationMatrixTransformationMatrixType)。

这种表示是 pypdfTransformation类的数学基础,也是我们理解后续所有方法的前提:任何平移、缩放、旋转,本质上都是在修改这 6 个数字

Transformation 类速览:构造与矩阵访问

Transformation定义于 pypdf/_page.py,并从pypdf顶层导出(见 pypdf/init.py),因此可以直接from pypdf import Transformation

构造函数签名如下:

Transformation(ctm: CompressedTransformationMatrix = (1, 0, 0, 1, 0, 0))

默认参数(1, 0, 0, 1, 0, 0)是单位矩阵——不做任何变换。例如:

from pypdf import Transformation op = Transformation() # 单位变换,什么都不做 op = Transformation((1, 0, 0, -1, 0, 0)) # 沿 x 轴的垂直镜像

类提供了两个与矩阵直接相关的接口:

  • matrix属性:把压缩六元组展开为 3×3 元组形式((a, b, 0), (c, d, 0), (e, f, 1)),便于阅读和参与数学运算;
  • compress(matrix)静态方法:是matrix的逆操作,把 3×3 矩阵压缩回(a, b, c, d, e, f)六元组。translatescalerotate等方法的内部实现都依赖它。

__repr__输出格式为Transformation(ctm=(a, b, c, d, e, f)),便于调试时观察当前矩阵状态。

核心变换操作:translate / scale / rotate

Transformation提供了三个构建基础变换的方法,每个方法都返回一个新的Transformation实例,原始对象不会被修改——这使它们可以像不可变操作一样安全地链式调用。

translate(tx, ty):平移

def translate(self, tx: float = 0, ty: float = 0) -> "Transformation"

在 x 轴方向平移tx,y 轴方向平移ty。从源码 pypdf/_page.py 可以看到实现非常直观:平移只影响矩阵的平移分量ef

m = self.ctm return Transformation(ctm=(m[0], m[1], m[2], m[3], m[4] + tx, m[5] + ty))

scale(sx, sy):缩放

def scale(self, sx: Optional[float] = None, sy: Optional[float] = None) -> "Transformation"

沿 x 轴缩放sx、沿 y 轴缩放sy。源码 pypdf/_page.py 中有两个值得注意的行为:

  1. sxsy都可选:若两者均为None,抛出ValueError;若只给一个,另一个自动取相同值(即等比例缩放);
  2. 缩放是**相对于坐标系原点(通常为页面左下角)**进行的,docstring 明确提示:如果需要相对页面其他位置缩放,可以结合translate调整页面内容或页面盒子。
op = Transformation().scale(2) # 等比例放大 2 倍 op = Transformation().scale(sx=2, sy=3) # x 轴 2 倍、y 轴 3 倍

rotate(rotation):旋转

def rotate(self, rotation: float) -> "Transformation"

角度制(degrees)旋转页面内容。实现中先通过math.radians转为弧度,再构造标准旋转矩阵:

rotation = math.radians(rotation) op = ( (math.cos(rotation), math.sin(rotation), 0), (-math.sin(rotation), math.cos(rotation), 0), (0, 0, 1), )

注意:旋转同样是围绕坐标系原点进行的,且正值方向取决于页面坐标系约定;旋转角度不限于 90° 的整数倍,任何角度都可用。

transform(m):复合变换

def transform(self, m: "Transformation") -> "Transformation"

将另一个变换m应用到当前变换上,返回新实例。从实现看,本质是 3×3 矩阵乘法:

ctm = Transformation.compress(matrix_multiply(self.matrix, m.matrix)) return Transformation(ctm)

matrix_multiply是通用矩阵乘法工具,定义于 pypdf/_utils.py。

docstring 中给出了用transform实现镜像的经典示例:

from pypdf import PdfWriter, Transformation height, width = 40, 50 page = PdfWriter().add_blank_page(800, 600) op = Transformation((1, 0, 0, -1, 0, height)) # 垂直镜像 op = Transformation().transform(Transformation((-1, 0, 0, 1, width, 0))) # 水平镜像 page.add_transformation(op)

链式调用示例

因为所有操作都返回新的Transformation,可以一行写出复合变换——这也正是类 docstring 的官方示例:

from pypdf import PdfWriter, Transformation page = PdfWriter().add_blank_page(800, 600) op = Transformation().scale(sx=2, sy=3).translate(tx=10, ty=20) page.add_transformation(op)

apply_on:把变换作用到单个点

def apply_on( self, pt: Union[tuple[float, float], list[float]], as_object: bool = False, ) -> Union[tuple[float, float], list[float]]

除了作用于整页内容,Transformation还可以直接计算单个点变换后的坐标。输入(x, y),返回(x′, y′);若传入list则返回list,传入tuple则返回tuple。参数as_object=True时,返回的元素类型为 pypdf 的FloatObject(适合直接写回 PDF 对象树),否则为普通float

变换公式在 pypdf/_page.py 中实现:

x′ = x * a + y * c + e y′ = x * b + y * d + f

例如,对一个translate(10, 20)后的变换调用apply_on((0, 0))会得到(10.0, 20.0)——这可以用于在写入前预览变换效果,或计算旋转后页面盒子的新角点。

将变换应用到页面:PageObject.add_transformation

仅仅构造Transformation对象还不会产生任何效果,真正让变换生效的是PageObject.add_transformation(),定义于 pypdf/_page.py:

def add_transformation( self, ctm: Union[Transformation, CompressedTransformationMatrix], expand: bool = False, ) -> None

参数说明:

  • ctm:既可以传Transformation对象,也可以直接传 6 元组(a, b, c, d, e, f)
  • expand:是否扩展页面以容纳变换后的内容,默认False

该方法与用户指南 docs/user/cropping-and-transforming.md 相互印证——该文档同时提到,对 90° 整数倍的旋转,页面级方法PageObject.rotate(90)通常比page.add_transformation(Transformation().rotate(...))更合适,因为前者会同时更新页面方向属性;而add_transformation适合任意角度与复合变换的场景。

底层做了什么:cm 操作符与内容流

从源码可以梳理出add_transformation的完整调用链:

  1. 若传入Transformation对象,先取出其ctm六元组;
  2. 通过内部方法_add_transformation_matrix把变换写入页面内容流;
  3. 调用content.isolate_graphics_state()隔离图形状态,确保变换只作用于目标内容、不影响页面其他部分;
  4. replace_contents(content)写回更新后的内容流。

变换最终会以 PDF 内容流操作符cm的形式写入文件。Transformation._to_cm()方法(pypdf/_page.py)负责生成这条指令,格式为:

a b c d e f cm

数值按 4 位小数格式化。cm是 PDF 中最基本的坐标变换操作符,阅读器看到它就会按照矩阵参数重新映射后续绘制的图形。

expand=True:自动扩展页面盒子

如果变换(如平移)导致内容超出了原页面边界,可以设置expand=True。源码会计算mediabox四个角点经变换后的新坐标,然后重新设置mediabox.lower_leftmediabox.upper_right,使页面盒子恰好包住变换后的内容:

self.mediabox.lower_left = (min(new_x), min(new_y)) self.mediabox.upper_right = (max(new_x), max(new_y))

注意它只调整媒体框(mediabox),不含裁剪等其他盒子。

完整实战:合成旋转、缩放与平移

下面是一个可直接运行的完整示例,演示如何读取 PDF、构造复合变换并写出结果文件:

from pypdf import PdfReader, PdfWriter, Transformation reader = PdfReader("input.pdf") writer = PdfWriter() for i, page in enumerate(reader.pages): if i == 0: # 第一页:逆时针旋转 45 度并放大 1.5 倍 op = Transformation().rotate(-45).scale(1.5) elif i == 1: # 第二页:水平镜像并向下平移 50 个单位 op = Transformation((-1, 0, 0, 1, page.mediabox.width, 0)).translate(ty=-50) else: # 其余页:放大 2 倍,并扩展页面以容纳新内容 op = Transformation().scale(2) page.add_transformation(op, expand=True) writer.add_page(page) with open("out-transformed.pdf", "wb") as f: writer.write(f)

与页面级 API 的关系

Transformation不是 pypdf 中唯一操作页面几何的途径,理解它们的分工有助于写出更恰当的代码:

  • PageObject.rotate(angle):页面级旋转,自动处理 90° 整数倍的页面方向,推荐用于纠正扫描方向;
  • PageObject.scale(sx, sy):页面级缩放,除变换内容外还会同步缩放bleedboxtrimboxartboxcropboxmediabox等全部页面盒子,并处理注解(见 pypdf/_page.py);
  • PageObject.merge_transformed_page(page2, ctm, over, expand):将另一页以给定变换矩阵叠加到当前页上;
  • PageObject.merge_translate(page2, tx, ty, over, expand)merge_transformed_page的便捷封装,其实现就是op = Transformation().translate(tx, ty)后调用merge_transformed_page(见 pypdf/_page.py)。

也就是说,Transformation是这些页面级 API 的底层数学引擎:凡是需要"任意矩阵变换"的场景,最终都会落到Transformation+cm操作符上。

小结

pypdf.Transformation用一个六元组完整表达了 PDF 的 2D 仿射变换,并通过translatescalerotatetransformapply_on等返回新实例的方法提供了安全、可链式调用的操作体验。配合PageObject.add_transformation()expand参数,开发者可以精确控制页面内容的缩放、平移、旋转与镜像;理解其底层的 3×3 矩阵乘法与cm操作符写入机制,则能在排查内容流问题时做到心中有数。

相关阅读:docs/modules/Transformation.rst(API 文档)、docs/user/cropping-and-transforming.md(裁剪与变换用户指南)、pypdf/_utils.py(矩阵乘法实现)。

【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询