绘图和图形#
注意
当这里提到“图纸”或“图形”时,我们指的是“矢量图形”或“线条艺术”。
因此,请将这些术语视为同义词!
PDF 文件作为其语法的一部分支持基本的绘图操作。这些是 矢量图形,包括基本的几何对象,如线条、曲线、圆、矩形,以及指定颜色。
此类操作的语法在Adobe PDF References第643页的“A操作符汇总”中定义。为PDF页面指定这些操作符发生在其contents对象中。
PyMuPDF 实现了大部分可用功能,通过其 Shape 类,该类类似于其他包中的“画布”概念(例如 reportlab)。
一个形状总是作为一个 页面的子对象 被创建,通常使用像 shape = page.new_shape() 这样的指令。这个类定义了许多在页面区域上执行绘图操作的方法。例如, last_point = shape.draw_rect(rect) 在适当定义的 rect = pymupdf.Rect(...) 的边界上绘制一个矩形。
返回的 last_point 始终 是绘图操作结束的 点(“最后一点”)。每个这样的基本绘图都需要后续的 Shape.finish() 来“结束”它,但可能会有多个绘图共享一个 finish() 方法。
事实上,Shape.finish() 定义了一组之前的绘制操作,以形成一个 – 可能相当复杂 – 图形对象。PyMuPDF 提供了几个预定义的图形在 shapes_and_symbols.py 中,展示了这一原理。
如果你导入这个脚本,你也可以直接使用它的图形,如下例所示:
# -*- coding: utf-8 -*-
"""
Created on Sun Dec 9 08:34:06 2018
@author: Jorj
@license: GNU AFFERO GPL V3
Create a list of available symbols defined in shapes_and_symbols.py
This also demonstrates an example usage: how these symbols could be used
as bullet-point symbols in some text.
"""
import pymupdf
import shapes_and_symbols as sas
# list of available symbol functions and their descriptions
tlist = [
(sas.arrow, "arrow (easy)"),
(sas.caro, "caro (easy)"),
(sas.clover, "clover (easy)"),
(sas.diamond, "diamond (easy)"),
(sas.dontenter, "do not enter (medium)"),
(sas.frowney, "frowney (medium)"),
(sas.hand, "hand (complex)"),
(sas.heart, "heart (easy)"),
(sas.pencil, "pencil (very complex)"),
(sas.smiley, "smiley (easy)"),
]
r = pymupdf.Rect(50, 50, 100, 100) # first rect to contain a symbol
d = pymupdf.Rect(0, r.height + 10, 0, r.height + 10) # displacement to next rect
p = (15, -r.height * 0.2) # starting point of explanation text
rlist = [r] # rectangle list
for i in range(1, len(tlist)): # fill in all the rectangles
rlist.append(rlist[i-1] + d)
doc = pymupdf.open() # create empty PDF
page = doc.new_page() # create an empty page
shape = page.new_shape() # start a Shape (canvas)
for i, r in enumerate(rlist):
tlist[i][0](shape, rlist[i]) # execute symbol creation
shape.insert_text(rlist[i].br + p, # insert description text
tlist[i][1], fontsize=r.height/1.2)
# store everything to the page's /Contents object
shape.commit()
import os
scriptdir = os.path.dirname(__file__)
doc.save(os.path.join(scriptdir, "symbol-list.pdf")) # save the PDF
这是脚本的结果:
如何提取图纸#
v1.18.0 新功能
页面发出的绘图命令 (矢量图形) 可以被提取为字典列表。有趣的是,这对于 所有支持的文档类型 都是可能的——不仅仅是 PDF:因此您也可以将其用于 XPS、EPUB 等其他格式。
页面方法, Page.get_drawings() 访问绘图命令并将它们转换为 Python 字典的列表。每个字典——称为“路径”——表示一个单独的绘图——它可以像一条简单的线一样简单,也可以是代表上一节形状的线条和曲线的复杂组合。
这个 path 字典的设计使得它可以被 Shape 类及其方法轻松使用。以下是一个包含一个路径的页面示例,它在 Rect(100, 100, 200, 200) 矩形内绘制一个红色边框的黄色圆形:
>>> pprint(page.get_drawings())
[{'closePath': True,
'color': [1.0, 0.0, 0.0],
'dashes': '[] 0',
'even_odd': False,
'fill': [1.0, 1.0, 0.0],
'items': [('c',
Point(100.0, 150.0),
Point(100.0, 177.614013671875),
Point(122.38600158691406, 200.0),
Point(150.0, 200.0)),
('c',
Point(150.0, 200.0),
Point(177.61399841308594, 200.0),
Point(200.0, 177.614013671875),
Point(200.0, 150.0)),
('c',
Point(200.0, 150.0),
Point(200.0, 122.385986328125),
Point(177.61399841308594, 100.0),
Point(150.0, 100.0)),
('c',
Point(150.0, 100.0),
Point(122.38600158691406, 100.0),
Point(100.0, 122.385986328125),
Point(100.0, 150.0))],
'lineCap': (0, 0, 0),
'lineJoin': 0,
'opacity': 1.0,
'rect': Rect(100.0, 100.0, 200.0, 200.0),
'width': 1.0}]
>>>
注意
您需要(至少)4条贝塞尔曲线(3阶)才能绘制出具有可接受精度的圆。有关背景信息,请参见此 Wikipedia article。
以下是一个代码片段,它提取页面的图形并在新页面上重新绘制它们:
import pymupdf
doc = pymupdf.open("some.file")
page = doc[0]
paths = page.get_drawings() # extract existing drawings
# this is a list of "paths", which can directly be drawn again using Shape
# -------------------------------------------------------------------------
#
# define some output page with the same dimensions
outpdf = pymupdf.open()
outpage = outpdf.new_page(width=page.rect.width, height=page.rect.height)
shape = outpage.new_shape() # make a drawing canvas for the output page
# --------------------------------------
# loop through the paths and draw them
# --------------------------------------
for path in paths:
# ------------------------------------
# draw each entry of the 'items' list
# ------------------------------------
for item in path["items"]: # these are the draw commands
if item[0] == "l": # line
shape.draw_line(item[1], item[2])
elif item[0] == "re": # rectangle
shape.draw_rect(item[1])
elif item[0] == "qu": # quad
shape.draw_quad(item[1])
elif item[0] == "c": # curve
shape.draw_bezier(item[1], item[2], item[3], item[4])
else:
raise ValueError("unhandled drawing", item)
# ------------------------------------------------------
# all items are drawn, now apply the common properties
# to finish the path
# ------------------------------------------------------
shape.finish(
fill=path["fill"], # fill color
color=path["color"], # line color
dashes=path["dashes"], # line dashing
even_odd=path.get("even_odd", True), # control color of overlaps
closePath=path["closePath"], # whether to connect last and first point
lineJoin=path["lineJoin"], # how line joins should look like
lineCap=max(path["lineCap"]), # how line ends should look like
width=path["width"], # line width
stroke_opacity=path.get("stroke_opacity", 1), # same value for both
fill_opacity=path.get("fill_opacity", 1), # opacity parameters
)
# all paths processed - commit the shape to its page
shape.commit()
outpdf.save("drawings-page-0.pdf")
可以看出,与Shape类的高一致性。唯一的例外是:出于技术原因,lineCap在这里是一个由3个数字组成的元组,而在Shape(以及在PDF中)是一个整数。因此我们只需取该元组的最大值。
这是一个通过之前的脚本创建的示例页面的输入和输出之间的比较:
注意
如这里所示,图形的重建并不完美。以下方面在此版本中将不会被复制:
页面定义可能很复杂,包括指示不显示/隐藏某些区域以保持它们不可见的指令。这些内容会被
Page.get_drawings()忽略 - 它将始终返回所有路径。
注意
您可以使用路径列表来制作自己的列表,例如页面上的所有线条或所有矩形,并通过条件(如颜色或在页面上的位置等)进行子选择。
如何删除图形#
要删除图形/矢量图形,我们必须使用一个 编辑注释,并指定图形的边界框,然后 添加并应用 一项编辑以将其删除。
下面的代码展示了如何删除页面上找到的第一个绘图:
paths = page.get_drawings()
rect = paths[0]["rect"] # rectangle of the 1st drawing
page.add_redact_annot(rect)
page.apply_redactions(0,2,1) # potentially set options for any of images, drawings, text
注意
请参见 Page.apply_redactions() 以获取可以发送的参数选项 - 您可以对被注释区域绑定的图像、绘图和文本对象应用删除选项。
如何绘制图形#
绘制图形就像调用你想要的Drawing Method类型一样简单。你可以直接在页面上或形状对象内绘制图形。
例如,绘制一个圆:
# Draw a circle on the page using the Page method
page.draw_circle((center_x, center_y), radius, color=(1, 0, 0), width=2)
# Draw a circle on the page using a Shape object
shape = page.new_shape()
shape.draw_circle((center_x, center_y), radius)
shape.finish(color=(1, 0, 0), width=2)
shape.commit(overlay=True)
形状对象可用于组合多个图形,这些图形应根据Shape.finish()指定的共同属性接收样式。