PDF编辑基础:图形和坐标系统

PDF(Portable Document Format)是一种广泛使用的文件格式,用于以一种独立于设备和平台的方式呈现文档。尽管PDF设计为一种最终的、类似于纸上墨水的格式,但在某些情况下,比如无法访问源格式时,编辑PDF文件成为必要。本文将探讨PDF文档中的图形基础,以及如何在必要时编辑这些图形。

PDF图形

PDF文档包含多种类型的信息,例如元数据(作者、标题等)、表单字段、导航数据(如书签)、注释(如评论)以及最重要的,图形。图形大致可以分为三类:曲线、文本和图像。PDF页面上的图形由一系列操作符描述。操作符可以分为三组:

  • 绘制操作符,用于绘制曲线、文本和图像
  • 图形状态操作符,用于执行如选择字体、选择颜色或转换坐标系统等操作
  • 标记内容操作符,将高级信息与图形关联,但不影响外观

每个操作符可以带零个或多个操作数。以下是一个绘制直线的简单示例:

150 250 m % 设置当前点到 (150, 250) 150 350 l % 向 (150, 350) 绘制直线 1 0 0 RG % 设置描边颜色为红色 S % 描边线条

操作符位于其使用的操作数之后。在第一行中,操作符 'm' 使用操作数 150 和 250。

以下是一个涉及文本的示例:

/F1 24 Tf % 设置字体为 F1,字体大小为 24 100 100 Td % 将文本位置移动到 (100, 100) (Hello World) Tj % 绘制文本 'Hello World'

在第一行中,操作符 Tf 接受操作数 /F1 和 24。操作数 /F1 是字体的名称。

以下是绘制图像的一行代码示例:

/I1 Do % 绘制图像

类似于选择字体,/I1 被解析为 PDF 文档中的图像。

图形状态

如上所述,操作符可以分为绘制操作符和图形状态操作符。当从上到下处理操作符时,会维护一个图形状态。图形状态操作符会改变图形状态,绘制操作符的结果会受到图形状态的影响。在第一个示例中,看到了 RG 操作符将描边颜色更改为红色,S 操作符使用当前的描边颜色绘制线条。

其他图形状态操作符可以设置线宽、虚线模式、填充颜色、字体大小等。最后,有两个特殊的操作符,分别保存(q)和恢复(Q)图形状态。简单来说:恢复操作符将图形状态更改回前一个保存操作符的状态。它们成对出现,并且可以嵌套。

坐标系统

PDF成像模型的一个关键部分是坐标系统。坐标系统决定了页面上给定坐标(如150, 250)的位置以及大小的范围。PDF定义了不同的坐标系统。最重要的两个是用户空间和设备空间。

设备空间由输出设备(如打印机或显示器)决定,PDF页面最终在该设备上呈现。假设想要将PDF页面渲染到300 DPI的Windows位图上,那么从Windows开发的角度来看,设备空间的原点在左上角,x轴指向右侧,y轴指向下方,单位长度(一个像素)是1/300英寸。

与设备空间相反,用户空间是设备独立的。对于每一页,它被初始化,使其原点位于左下角,x轴指向右侧,y轴指向上方,单位长度是1/72英寸或1点。上述PDF操作符示例中的坐标是在用户空间中。

将用户空间映射到设备空间

用户空间中的坐标如何转换到设备空间中的坐标由当前变换矩阵或CTM定义。让看看这在代码中是如何实现的:

C# float width = 612; // Letter页面的宽度 float height = 792; // Letter页面的高度 float dpi = 600; // 输出设备是600 dpi位图 Bitmap bitmap = new Bitmap((int)(width * dpi / 72), (int)(height * dpi / 72)); PointF[] points = new PointF[] { new PointF(0, 0), // 左下角 new PointF(0, height), new PointF(width, height), new PointF(width, 0) }; Console.WriteLine(string.Join("; ", points.Select(p => string.Format("({0}, {1})", p.X, p.Y)))); Matrix ctm = new Matrix(); ctm.Scale(1, -1); // 垂直翻转轴 ctm.Translate(0, -bitmap.Height); ctm.Scale(dpi / 72f, dpi / 72); ctm.TransformPoints(points); Console.WriteLine(string.Join("; ", points.Select(p => string.Format("({0}, {1})", (int)p.X, (int)p.Y))));

通过这段代码,可以将用户空间中的点转换为设备空间中的点。

改变用户空间

CTM是图形状态的一部分,可以使用cm操作符进行更改。cm操作符接受六个操作数,代表一个变换矩阵。更改CTM将影响随后的绘制操作符,如下例所示。

有一个200点乘以200点的页面。以下图像显示了用户空间坐标系统叠加在空白页面上:

绘制一个50乘以50的红色正方形和一个25乘以25的较小蓝色正方形在红色正方形内部,如下所示:

接下来,通过平移(50, 75)来变换用户空间。注意,这是在绘制图形之前完成的。

最后,用户空间旋转30度,如下所示:

因此,不是变换正方形,而是变换用户空间,然后在该用户空间内绘制正方形。这可能会让人感到反直觉。

形状

从开发的角度来看,一系列操作符并不是一个方便的格式。例如,不能轻松地导航到页面上的图像并检索其位置。它的属性取决于所有先前操作符的累积,所以首先需要处理它们。对于文本和曲线也是如此。

更改图形,如移动单个图像或旋转文本片段,会更加困难,因为必须插入操作符,以便它们只影响目标图形。

PDFKit.NET

PDFKit.NET允许将页面上的所有图形提取为形状对象集合。在内部,它将执行解释操作符的艰苦工作,从绘制操作符创建形状对象,并分配反映当前图形状态的属性。提取形状后,可以删除形状,插入新形状并更改它们各自的属性。完成后,可以将形状写回到PDF页面。这将反过来生成所需的操作符和操作数序列。

为了演示使用形状编辑图形,将替换一个徽标。请参见下面原始PDF和替换徽标后的PDF图像:

C# static void Main(string[] args) { using (FileStream fileIn = new FileStream("indesign_shortcuts.pdf", FileMode.Open, FileAccess.Read)) { Document pdfIn = new Document(fileIn); Document pdfOut = new Document(); foreach (Page page in pdfIn.Pages) { ShapeCollection shapes = page.CreateShapes(); replaceLogo(shapes); // 添加修改后的形状到新文档 Page newPage = new Page(page.Width, page.Height); newPage.Overlay.Add(shapes); pdfOut.Pages.Add(newPage); } using (FileStream fileOut = new FileStream("out.pdf", FileMode.Create, FileAccess.Write)) { pdfOut.Write(fileOut); } } } static void replaceLogo(ShapeCollection shapes) { for (int i = 0; i < shapes.Count; i++) { Shape shape = shapes[i]; if (shape is ShapeCollection) { // 递归 replaceLogo(shape as ShapeCollection); } else if (shape is ImageShape) { ImageShape oldLogo = shape as ImageShape; shapes.RemoveAt(i); ImageShape newLogo = new ImageShape("new-logo.png"); newLogo.Transform = oldLogo.Transform; newLogo.Width = oldLogo.Width; newLogo.Height = oldLogo.Height; shapes.Insert(i, newLogo); } } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485