PyMuPDF 1.24.4 中文文档(三)(4)https://developer.aliyun.com/article/1559533
如何使用墨迹注释
墨迹注释用于包含自由手绘涂鸦。典型示例可能是您签名的图像,由名字和姓氏组成。技术上,墨迹注释实现为点列表的列表。每个点列表被视为连接点的连续线。不同的点列表表示注释的独立线段。
下面的脚本创建了一个带有两个数学曲线(正弦和余弦函数图形)作为线段的墨迹注释:
import math import pymupdf #------------------------------------------------------------------------------ # preliminary stuff: create function value lists for sine and cosine #------------------------------------------------------------------------------ w360 = math.pi * 2 # go through full circle deg = w360 / 360 # 1 degree as radians rect = pymupdf.Rect(100,200, 300, 300) # use this rectangle first_x = rect.x0 # x starts from left first_y = rect.y0 + rect.height / 2. # rect middle means y = 0 x_step = rect.width / 360 # rect width means 360 degrees y_scale = rect.height / 2. # rect height means 2 sin_points = [] # sine values go here cos_points = [] # cosine values go here for x in range(362): # now fill in the values x_coord = x * x_step + first_x # current x coordinate y = -math.sin(x * deg) # sine p = (x_coord, y * y_scale + first_y) # corresponding point sin_points.append(p) # append y = -math.cos(x * deg) # cosine p = (x_coord, y * y_scale + first_y) # corresponding point cos_points.append(p) # append #------------------------------------------------------------------------------ # create the document with one page #------------------------------------------------------------------------------ doc = pymupdf.open() # make new PDF page = doc.new_page() # give it a page #------------------------------------------------------------------------------ # add the Ink annotation, consisting of 2 curve segments #------------------------------------------------------------------------------ annot = page.addInkAnnot((sin_points, cos_points)) # let it look a little nicer annot.set_border(width=0.3, dashes=[1,]) # line thickness, some dashing annot.set_colors(stroke=(0,0,1)) # make the lines blue annot.update() # update the appearance page.draw_rect(rect, width=0.3) # only to demonstrate we did OK doc.save("a-inktest.pdf")
这就是结果:
您对本页面有任何反馈意见吗?
本软件按原样提供,不提供任何明示或暗示的保证。本软件根据许可证分发,未经许可不得复制、修改或分发。请参阅artifex.com上的许可信息,或联系美国加利福尼亚州旧金山 Mesa 街 39 号 108A 套房的 Artifex Software Inc.获取更多信息。
本文档覆盖了截至 1.24.4 版本的所有版本。
绘图和图形
原文:
pymupdf.readthedocs.io/en/latest/recipes-drawing-and-graphics.html
注意
当提到“绘图”或“图形”时,我们指的是“矢量图形”或“线条艺术”。
因此,请将这些术语视为同义词!
PDF 文件支持作为其语法一部分的基本绘图操作。这些是矢量图形,包括诸如线条、曲线、圆、矩形以及指定颜色的基本几何对象。
此类操作的语法在 Adobe PDF References 的第 643 页中的“A Operator Summary”中定义。为 PDF 页面指定这些运算符发生在其 contents
对象中。
PyMuPDF 通过其 Shape 类实现了大部分可用功能,这类似于其他软件包中的“画布”概念(例如 reportlab)。
一个形状始终是页面的子级,通常使用类似 shape = page.new_shape()
的指令创建。该类定义了许多在页面区域上执行绘图操作的方法。例如,last_point = shape.draw_rect(rect)
可以沿着适当定义的 rect = pymupdf.Rect(...)
边界绘制一个矩形。
返回的 last_point 总是 绘制操作结束的 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 # 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 字典列表。每个称为“路径”的字典代表一个单独的绘图,它可能简单如一条线,或是前一节中形状的复杂组合之一。
路径字典被设计为可以轻松由形状类及其方法使用。这里有一个页面的示例,其中绘制了一个红边黄色圆在矩形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 文章了解一些背景知识。
以下是一个代码片段,它提取页面的图形并将它们重新绘制在新页面上:
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")
如所示,与形状类有很高的一致性水平。只有一个例外:出于技术原因,这里的lineCap
是一个包含 3 个数字的元组,而在形状(以及 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()
以获取可以发送的参数选项——您可以将删除选项应用于受注释区域约束的图像、绘图和文本对象。
如何绘制图形
绘制图形就像调用你可能想要的绘制方法
类型一样简单。你可以直接在页面上或形状对象内绘制图形。
例如,要绘制一个圆:
# 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()
所指定。
您对此页面有任何反馈吗?
此软件按原样提供,不提供任何明示或暗示的担保。此软件按许可分发,未经许可明确授权的情况下,不得复制、修改或分发此软件。请参阅artifex.com获取许可信息或联系 Artifex Software Inc.,39 Mesa Street,Suite 108A,San Francisco CA 94129,美国,以获取进一步信息。
此文档涵盖所有版本,直至 1.24.4。
## 如何提取绘图
- v1.18.0 中的新功能
页面发出的绘图命令(矢量图形)可以提取为字典列表。有趣的是,这对于所有支持的文档类型都适用 - 不仅限于 PDF:因此,您可以在 XPS、EPUB 和其他格式中使用它。
页面方法,Page.get_drawings()
访问绘图命令并将其转换为 Python 字典列表。每个字典 - 称为“路径” - 表示单独的绘图,它可能简单如一条直线,或复杂如前一节形状之一的线条和曲线组合。
路径字典已经设计得可以轻松地被 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 个三阶贝塞尔曲线才能以可接受的精度绘制一个圆。请参阅这篇Wikipedia 文章了解背景信息。
下面是一个代码片段,用于提取页面的绘图并将其重新绘制到新页面上:
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()
指定的公共属性进行设置。
您对此页面有任何反馈吗?
本软件按原样提供,不附带任何明示或暗示的担保。本软件在许可下分发,并且未经授权明确许可的情况下不得复制、修改或分发。有关更多信息,请参阅artifex.com,或联系位于美国加利福尼亚州旧金山 94129 Mesa Street 39 号 108A 套房的 Artifex Software Inc.。
此文档涵盖所有版本,直至 1.24.4。