跳转至

使用 pdfgen 进行图形和文本处理

2.1 基本概念

pdfgen 包是生成 PDF 文档的最低层接口。一个 pdfgen 程序本质上 是一系列指令,用于将文档"绘制"到一系列页面上。提供绘制操作的 接口对象就是 pdfgen canvas

画布可以被想象成一张白纸,纸上的点使用笛卡尔 (X,Y) 坐标系来标识, 默认情况下 (0,0) 原点位于页面的左下角。此外,第一个坐标 x 向右延伸, 第二个坐标 y 向上延伸。

下面是一个使用画布的简单示例程序。

    from reportlab.pdfgen import canvas
    def hello(c):
        c.drawString(100,100,"Hello World")
    c = canvas.Canvas("hello.pdf")
    hello(c)
    c.showPage()
    c.save()

上面的代码创建了一个 canvas 对象,它将在当前工作目录中生成 一个名为 hello.pdf 的 PDF 文件。然后它调用 hello 函数, 将 canvas 作为参数传入。最后,showPage 方法保存画布的当前页面, save 方法存储文件并关闭画布。

showPage 方法使 canvas 停止在当前页面上绘制, 后续的任何操作将在后续页面上绘制(如果有的话——如果没有后续操作, 则不会创建新页面)。save 方法必须在文档构建完成后调用—— 它生成 PDF 文档,这也是 canvas 对象的全部目的。

2.2 关于画布的更多信息

在描述绘制操作之前,我们先岔开话题,介绍一下可以用来配置画布的一些操作。 有许多不同的设置可供使用。如果您是 Python 新手,或者迫不及待地想要生成一些输出, 您可以先跳过这一部分,但稍后请回来阅读!

首先,我们来看看画布的构造函数参数:

    def __init__(self,filename,
                 pagesize=(595.27,841.89),
                 bottomup = 1,
                 pageCompression=0,
                 encoding=rl_config.defaultEncoding,
                 verbosity=0
                 encrypt=None):

filename 参数控制最终 PDF 文件的名称。 您也可以传入任何打开的二进制流(例如 sys.stdout,即带有二进制编码的 Python 进程标准输出), PDF 文档将写入该流。由于 PDF 是二进制格式,在它之前或之后写入其他内容时需要小心; 您不能在 HTML 页面中间内联传递 PDF 文档!

pagesize 参数是一个由两个数字组成的元组,单位为磅(1/72 英寸)。 画布默认为 A4(一种国际标准页面尺寸,与美国标准的 letter 页面尺寸不同), 但最好明确指定。大多数常见的页面尺寸可以在库模块 reportlab.lib.pagesizes 中找到, 因此您可以使用如下表达式

from reportlab.lib.pagesizes import letter, A4
myCanvas = Canvas('myfile.pdf', pagesize=letter)
width, height = letter  #keep for later

如果您的文档打印出现问题,请确保您使用了正确的页面尺寸(通常是 A4letter)。 有些打印机在页面过大或过小时无法正常工作。

很多时候,您需要根据页面尺寸进行计算。在上面的示例中,我们提取了宽度和高度。 在程序的后面部分,我们可以使用 width 变量来定义右边距为 width - inch, 而不是使用常量。使用变量后,即使页面尺寸发生变化,边距仍然合理。

bottomup 参数用于切换坐标系。某些图形系统(如 PDF 和 PostScript) 将 (0,0) 放在页面左下角,而其他系统(如许多图形用户界面 [GUI]) 将原点放在左上角。bottomup 参数已被弃用,未来可能会被移除

TODO: 需要验证它是否在所有任务中都能正常工作,如果不能,则将其移除

pageCompression 选项决定每页的 PDF 操作流是否被压缩。 默认情况下页面流不被压缩,因为压缩会减慢文件生成过程。 如果输出大小很重要,请设置 pageCompression=1,但请记住, 压缩后的文档会更小,但生成速度会更慢。请注意,图像总是被压缩的, 此选项仅在每页有大量文本和矢量图形时才能节省空间。

encoding 参数在 2.0 版本中已基本过时,99% 的用户可以忽略它。 其默认值即可满足需求,除非您特别需要使用 MacRoman 中存在而 Winansi 中不存在的 约 25 个字符。相关参考请参见: http://www.alanwood.net/demos/charsetdiffs.html

该参数决定标准 Type 1 字体使用的字体编码;这应该与您系统上的编码相对应。 请注意,这是字体内部使用的编码;您传递给 ReportLab 工具包进行渲染的文本 应始终是 Python unicode 字符串对象或 UTF-8 编码的字节字符串(参见下一章)! 字体编码目前有两个值:'WinAnsiEncoding''MacRomanEncoding'。 上面的变量 rl_config.defaultEncoding 指向前者,这是 Windows、Mac OS X 和许多 Unix 系统(包括 Linux)上的标准。如果您是 Mac 用户且没有 OS X, 可能需要进行全局更改:修改 reportlab/pdfbase/pdfdoc.py 顶部的行来切换。 否则,您可能完全可以忽略此参数,永远不需要传递它。 对于所有 TTF 字体和常用的 CID 字体,此处传入的编码会被忽略, 因为 ReportLab 库本身知道这些字体的正确编码。

演示脚本 reportlab/demos/stdfonts.py 会打印两份测试文档, 显示所有字体中的所有码点,以便您查找字符。特殊字符可以使用通常的 Python 转义序列插入到字符串命令中;例如 \101 = 'A'。

verbosity 参数决定打印多少日志信息。默认为零, 以帮助需要从标准输出捕获 PDF 的应用程序。当值为 1 时, 每次生成文档时您都会收到一条确认消息。更高的数字将来可能会提供更多输出。

encrypt 参数决定文档是否加密以及如何加密。 默认情况下,文档不加密。如果 encrypt 是一个字符串对象, 它将用作 PDF 的用户密码。如果 encryptreportlab.lib.pdfencrypt.StandardEncryption 的一个实例,则使用该对象来加密 PDF。这允许对加密设置进行更细粒度的控制。 加密将在第 4 章中更详细地介绍。

TODO: 待完成 - 所有信息函数和其他非绘制内容

TODO: 涵盖所有构造函数参数,以及 setAuthor 等。

2.3 绘制操作

假设上面引用的 hello 函数实现如下(我们暂不详细解释每个操作)。

def hello(c):
    from reportlab.lib.units import inch
    # move the origin up and to the left
    c.translate(inch,inch)
    # define a large font
    c.setFont("Helvetica", 14)
    # choose some colors
    c.setStrokeColorRGB(0.2,0.5,0.3)
    c.setFillColorRGB(1,0,1)
    # draw some lines
    c.line(0,0,0,1.7*inch)
    c.line(0,0,1*inch,0)
    # draw a rectangle
    c.rect(0.2*inch,0.2*inch,1*inch,1.5*inch, fill=1)
    # make text go straight up
    c.rotate(90)
    # change color
    c.setFillColorRGB(0,0,0.77)
    # say hello (note after rotate the y coord needs to be negative!)
    c.drawString(0.3*inch, -inch, "Hello World")

查看这段代码,可以注意到使用画布执行的操作本质上分为两种类型。 第一种类型在页面上绘制内容,例如文本字符串、矩形或线条。 第二种类型更改画布的状态,例如更改当前填充或描边颜色, 或更改当前字体的类型和大小。

如果我们将程序想象成一位在画布上工作的画家,"绘制"操作使用当前的 工具集(颜色、线条样式、字体等)在画布上涂色, 而"状态更改"操作则更改当前工具之一(例如将填充颜色从之前的颜色更改为蓝色, 或将当前字体更改为 15 磅的 Times-Roman)。

上面列出的"Hello World"程序生成的文档将包含以下图形。

关于本文档中的演示

本文档包含所讨论代码的演示,如上面矩形中所示的示例。 这些演示绘制在嵌入在本指南实际页面中的"迷你页面"上。 迷你页面宽 5.5 英寸,高 3 英寸。演示显示的是演示代码的实际输出。 为方便起见,输出的大小略有缩小。

2.4 工具:绘图操作

本节简要列出了程序可用于通过画布接口在页面上绘制信息的可用工具。 这些工具将在后面的章节中详细讨论。此处列出是为了方便参考和总结。

线条方法

canvas.line(x1,y1,x2,y2)
canvas.lines(linelist)

线条方法在画布上绘制直线段。

形状方法

canvas.grid(xlist, ylist) 
canvas.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
canvas.arc(x1,y1,x2,y2) 
canvas.rect(x, y, width, height, stroke=1, fill=0) 
canvas.ellipse(x1,y1, x2,y2, stroke=1, fill=0)
canvas.wedge(x1,y1, x2,y2, startAng, extent, stroke=1, fill=0) 
canvas.circle(x_cen, y_cen, r, stroke=1, fill=0)
canvas.roundRect(x, y, width, height, radius, stroke=1, fill=0) 

形状方法在画布上绘制常见的复杂形状。

字符串绘制方法

canvas.drawString(x, y, text):
canvas.drawRightString(x, y, text) 
canvas.drawCentredString(x, y, text)

字符串绘制方法在画布上绘制单行文本。

文本对象方法

textobject = canvas.beginText(x, y) 
canvas.drawText(textobject) 

文本对象用于以 canvas 接口不直接支持的方式格式化文本。 程序使用 beginTextcanvas 创建文本对象, 然后通过调用 textobject 的方法来格式化文本。 最后使用 drawTexttextobject 绘制到画布上。

路径对象方法

path = canvas.beginPath() 
canvas.drawPath(path, stroke=1, fill=0, fillMode=None) 
canvas.clipPath(path, stroke=1, fill=0, fillMode=None) 

路径对象类似于文本对象:它们为执行画布接口不直接提供的复杂图形绘制 提供了专门的控制。程序使用 beginPath 创建路径对象, 使用路径对象的方法用图形填充路径, 然后使用 drawPath 在画布上绘制路径。

也可以使用 clipPath 方法将路径用作"裁剪区域"—— 例如,可以使用圆形路径裁剪掉矩形图像的外部部分, 使页面上只显示图像的圆形部分。

如果指定了 fill=1,则可以使用 fillMode 参数设置 0=even-odd 或 1=non-zero 填充模式。 这将改变复杂路径的填充方式。如果使用默认值 None, 则使用画布的 _fillMode 属性值(通常为 0,即 even-odd)。

图像方法

您需要 Python Imaging Library (PIL) 才能在 ReportLab 包中使用图像。 运行 tests 子目录中的脚本 test_pdfgen_general.py 并查看输出的第 7 页, 可以找到下面技术的示例。

有两种听起来相似的图像绘制方法。首选方法是 drawImage。 它实现了缓存系统,因此您可以定义一次图像并多次绘制; 它在 PDF 文件中只会存储一次。drawImage 还公开了一个高级参数—— 透明蒙版,未来还会公开更多参数。较旧的技术 drawInlineImage 将位图存储在页面流中, 因此如果您在文档中多次使用同一图像会非常低效;但如果图像非常小且不重复, 则可以生成渲染更快的 PDF。我们先讨论最旧的方法:

canvas.drawInlineImage(self, image, x,y, width=None,height=None) 

drawInlineImage 方法将图像放置在画布上。image 参数可以是 PIL Image 对象 或图像文件名。支持许多常见的文件格式,包括 GIF 和 JPEG。 它返回实际图像的像素大小,以 (width, height) 元组形式表示。

canvas.drawImage(self, image, x,y, width=None,height=None,mask=None) 

参数和返回值与 drawInlineImage 相同。但是,我们使用了缓存系统; 给定的图像仅在第一次使用时存储,后续使用时只进行引用。 如果您提供文件名,它假定相同的文件名意味着相同的图像。 如果您提供 PIL 图像,它会测试内容是否实际发生了更改,然后再重新嵌入。

mask 参数让您可以创建透明图像。它接受 6 个数字, 定义将被遮罩或视为透明的 RGB 值范围。例如,使用 [0,2,40,42,136,139], 它将遮盖红色值为 0 或 1、绿色值为 40 或 41、蓝色值为 136、137 或 138 的任何像素(在 0-255 的范围内)。目前您需要自己知道哪种颜色是"透明的" 或背景色。

PDF 支持许多图像功能,我们将随着时间的推移公开更多功能, 可能会通过向 drawImage 添加额外的关键字参数来实现。

结束页面

canvas.showPage()

showPage 方法结束当前页面。所有后续绘制将在另一页上进行。

警告!在 pdfgen 中前进到新页面时,所有状态更改(字体更改、颜色设置、几何变换等) 都会被遗忘。任何您希望保留的状态设置都必须在程序继续绘制之前重新设置!

2.5 工具箱:状态更改操作

本节简要列出了切换程序通过 canvas 接口在页面上绘制信息所用工具的方法。 这些方法也将在后面的章节中详细讨论。

更改颜色

canvas.setFillColorCMYK(c, m, y, k) 
canvas.setStrikeColorCMYK(c, m, y, k) 
canvas.setFillColorRGB(r, g, b) 
canvas.setStrokeColorRGB(r, g, b) 
canvas.setFillColor(acolor) 
canvas.setStrokeColor(acolor) 
canvas.setFillGray(gray) 
canvas.setStrokeGray(gray) 

PDF 支持三种不同的颜色模型:灰度、加色(红/绿/蓝即 RGB) 和带明暗参数的减色(青/品红/黄/黑即 CMYK)。 ReportLab 包还提供了诸如 lawngreen 等命名颜色。 图形状态中有两个基本颜色参数:用于图形内部填充的 Fill 颜色 和用于图形边界描边的 Stroke 颜色。上述方法支持使用四种颜色规格中的任何一种 来设置填充或描边颜色。

更改字体

canvas.setFont(psfontname, size, leading = None) 

setFont 方法将当前文本字体更改为给定的类型和大小。 leading 参数指定从一行文本前进到下一行时向下移动的距离。

更改图形线条样式

canvas.setLineWidth(width) 
canvas.setLineCap(mode) 
canvas.setLineJoin(mode) 
canvas.setMiterLimit(limit) 
canvas.setDash(self, array=[], phase=0) 

PDF 中绘制的线条可以呈现多种图形样式。 线条可以有不同的宽度,可以以不同的端点样式结束, 可以以不同的连接样式相交,可以是连续的,也可以是点线或虚线。 上述方法调整这些不同的参数。

更改几何变换

canvas.setPageSize(pair) 
canvas.transform(a,b,c,d,e,f): 
canvas.translate(dx, dy) 
canvas.scale(x, y) 
canvas.rotate(theta) 
canvas.skew(alpha, beta) 

所有 PDF 绘制都适应指定的页面大小。在指定页面大小之外绘制的元素不可见。 此外,所有绘制的元素都经过仿射变换,该变换可能会调整它们的位置 和/或扭曲它们的外观。setPageSize 方法调整当前页面大小。 transformtranslatescalerotateskew 方法 向当前变换添加额外的变换。重要的是要记住,这些变换是增量的—— 新变换修改当前变换(但不会替换它)。

状态控制

canvas.saveState() 
canvas.restoreState() 

通常,保存当前字体、图形变换、线条样式和其他图形状态以便稍后恢复是很重要的。 saveState 方法标记当前图形状态,以便稍后通过匹配的 restoreState 进行恢复。 请注意,保存和恢复方法的调用必须匹配——restore 调用将状态恢复到 最近保存但尚未恢复的状态。但是,您不能在一个页面上保存状态 然后在下一个页面上恢复——页面之间不保留任何状态。

2.6 其他 canvas 方法

并非所有 canvas 对象的方法都属于"工具"或"工具箱"类别。 以下是一些不太容易归类的杂项方法,列在此处是为了完整性。

 canvas.setAuthor()
 canvas.addOutlineEntry(title, key, level=0, closed=None)
 canvas.setTitle(title)
 canvas.setSubject(subj)
 canvas.pageHasData()
 canvas.showOutline()
 canvas.bookmarkPage(name)
 canvas.bookmarkHorizontalAbsolute(name, yhorizontal)
 canvas.doForm()
 canvas.beginForm(name, lowerx=0, lowery=0, upperx=None, uppery=None)
 canvas.endForm()
 canvas.linkAbsolute(contents, destinationname, Rect=None, addtopage=1, name=None, **kw)
 canvas.linkRect(contents, destinationname, Rect=None, addtopage=1, relative=1, name=None, **kw)
 canvas.getPageNumber()
 canvas.addLiteral()
 canvas.getAvailableFonts()
 canvas.stringWidth(self, text, fontName, fontSize, encoding=None)
 canvas.setPageCompression(onoff=1)
 canvas.setPageTransition(self, effectname=None, duration=1,
                        direction=0,dimension='H',motion='I')

2.7 坐标系(默认用户空间)

默认情况下,页面上的位置由一对数字标识。例如,(4.5*inch, 1*inch) 标识了从左下角开始向右移动 4.5 英寸、向上移动 1 英寸的位置。

例如,以下函数在 canvas 上绘制了多个元素。

def coords(canvas):
    from reportlab.lib.units import inch
    from reportlab.lib.colors import pink, black, red, blue, green
    c = canvas
    c.setStrokeColor(pink)
    c.grid([inch, 2*inch, 3*inch, 4*inch], [0.5*inch, inch, 1.5*inch, 2*inch, 2.5*inch])
    c.setStrokeColor(black)
    c.setFont("Times-Roman", 20)
    c.drawString(0,0, "(0,0) the Origin")
    c.drawString(2.5*inch, inch, "(2.5,1) in inches")
    c.drawString(4*inch, 2.5*inch, "(4, 2.5)")
    c.setFillColor(red)
    c.rect(0,2*inch,0.2*inch,0.3*inch, fill=1)
    c.setFillColor(green)
    c.circle(4.5*inch, 0.4*inch, 0.2*inch, fill=1)

在默认用户空间中,"原点" (0,0) 位于左下角。 在默认用户空间中执行 coords 函数(针对"演示迷你页面"),我们得到以下结果。

移动原点:translate 方法

通常,将"原点"移动到离开左下角的新位置是很有用的。 canvas.translate(*x,y*) 方法将当前页面的原点移动到当前由 (x,y) 标识的位置。

例如,以下 translate 函数首先移动原点,然后绘制与上面相同的对象。

def translate(canvas):
    from reportlab.lib.units import cm
    canvas.translate(2.3*cm, 0.3*cm)
    coords(canvas)

这会产生以下结果。

注意:如示例所示,将对象或对象的一部分绘制在"页面之外"是完全可能的。 特别是,一个常见的令人困惑的错误是平移操作将整个绘制移到了页面的可见区域之外。 如果程序生成了空白页面,可能所有绘制的对象都在页面之外。

缩放:scale 操作

另一个重要的操作是缩放。缩放操作 canvas.scale(*dx,dy*) 分别按 dxdy 因子拉伸或缩小 xy 维度。 通常 dxdy 是相同的——例如,要在所有维度上将绘制缩小一半, 使用 dx = dy = 0.5。但为了说明的目的,我们展示一个 dxdy 不同的示例。

def scale(canvas):
    canvas.scale(0.75, 0.5)
    coords(canvas)

这会产生之前显示操作的"矮胖"缩小版本。

注意:缩放也可能将对象或对象的部分移出页面, 或者可能导致对象"缩小到消失"。

缩放和平移可以组合使用,但操作的顺序很重要。

def scaletranslate(canvas):
    from reportlab.lib.units import inch
    canvas.setFont("Courier-BoldOblique", 12)
    # save the state
    canvas.saveState()
    # scale then translate
    canvas.scale(0.3, 0.5)
    canvas.translate(2.4*inch, 1.5*inch)
    canvas.drawString(0, 2.7*inch, "Scale then translate")
    coords(canvas)
    # forget the scale and translate...
    canvas.restoreState()
    # translate then scale
    canvas.translate(2.4*inch, 1.5*inch)
    canvas.scale(0.3, 0.5)
    canvas.drawString(0, 2.7*inch, "Translate then scale")
    coords(canvas)

这个示例函数首先保存当前 canvas 状态, 然后执行 scale 操作,接着执行 translate 操作。 之后函数恢复状态(有效地移除缩放和平移的效果), 然后以不同的顺序执行相同的操作。请注意下面的效果。

注意:缩放会缩小或放大所有内容,包括线宽, 因此使用 canvas.scale 方法以缩放的微观单位渲染微观绘制 可能会产生一个团块(因为所有线宽都会被放大大幅增加)。 同样,将以米为单位的飞机机翼缩放到厘米渲染时,可能会导致线条 缩小到消失的程度。对于工程或科学目的, 请在使用画布渲染之前在外部缩放和平移单位。

保存和恢复 canvas 状态:saveStaterestoreState

scaletranslate 函数使用了 canvas 对象的一个重要特性: 保存和恢复 canvas 当前参数的能力。 通过将一系列操作放在匹配的 canvas.saveState()canvas.restoreState() 操作对中,所有字体、颜色、线条样式、缩放、平移或 canvas 图形状态 的其他方面的更改都可以恢复到 saveState() 时的状态。 请记住,保存/恢复调用必须匹配:一个多余的保存或恢复操作可能导致 意外的不良行为。此外,请记住页面之间保留任何 canvas 状态, 保存/恢复机制不能跨页面使用。

镜像

值得注意的是,缩放因子可以为负数,尽管这可能不是特别有用。 例如以下函数

def mirror(canvas):
    from reportlab.lib.units import inch
    canvas.translate(5.5*inch, 0)
    canvas.scale(-1.0, 1.0)
    coords(canvas)

创建了由 coord 函数绘制的元素的镜像。

请注意,文本字符串是反向绘制的。

2.8 颜色

根据 PDF 将使用的介质,PDF 中通常有两种类型的颜色。 最广为人知的屏幕颜色模型 RGB 可以在 PDF 中使用, 但在专业印刷中,另一种颜色模型 CMYK 主要被使用, 它对油墨如何应用到纸张上提供了更多控制。下面详细介绍这些颜色模型。

RGB 颜色

RGB 即加色表示遵循计算机屏幕通过添加不同强度的红光、绿光和蓝光 来混合出中间任意颜色的方式,其中白色是将三色光全部开到最大(1,1,1)形成的。

pdfgen 中有三种指定 RGB 颜色的方式:通过名称(使用 color 模块)、 通过红/绿/蓝(加色,RGB)值,或通过灰度级别。 下面的 colors 函数演示了这四种方法中的每一种。

def colorsRGB(canvas):
    from reportlab.lib import colors
    from reportlab.lib.units import inch
    black = colors.black
    y = x = 0; dy=inch*3/4.0; dx=inch*5.5/5; w=h=dy/2; rdx=(dx-w)/2
    rdy=h/5.0; texty=h+2*rdy
    canvas.setFont("Helvetica",10)
    for [namedcolor, name] in (
           [colors.lavenderblush, "lavenderblush"],
           [colors.lawngreen, "lawngreen"],
           [colors.lemonchiffon, "lemonchiffon"],
           [colors.lightblue, "lightblue"],
           [colors.lightcoral, "lightcoral"]):
        canvas.setFillColor(namedcolor)
        canvas.rect(x+rdx, y+rdy, w, h, fill=1)
        canvas.setFillColor(black)
        canvas.drawCentredString(x+dx/2, y+texty, name)
        x = x+dx
    y = y + dy; x = 0
    for rgb in [(1,0,0), (0,1,0), (0,0,1), (0.5,0.3,0.1), (0.4,0.5,0.3)]:
        r,g,b = rgb
        canvas.setFillColorRGB(r,g,b)
        canvas.rect(x+rdx, y+rdy, w, h, fill=1)
        canvas.setFillColor(black)
        canvas.drawCentredString(x+dx/2, y+texty, "r%s g%s b%s"%rgb)
        x = x+dx
    y = y + dy; x = 0
    for gray in (0.0, 0.25, 0.50, 0.75, 1.0):
        canvas.setFillGray(gray)
        canvas.rect(x+rdx, y+rdy, w, h, fill=1)
        canvas.setFillColor(black)
        canvas.drawCentredString(x+dx/2, y+texty, "gray: %s"%gray)
        x = x+dx

RGB 颜色透明度

pdfgen 中,对象可以绘制在其他对象之上以获得良好的效果。 通常有两种处理空间上重叠对象的方式,默认情况下顶层对象将遮盖 位于其下方的任何其他对象的部分。如果您需要透明效果,有两个选择:

  1. 如果您的文档打算以专业方式印刷,并且您在 CMYK 颜色空间中工作, 则可以使用 overPrint(叠印)。在叠印模式下,颜色在打印机中物理混合, 从而产生新的颜色。默认情况下会应用 knockout(挖空),只显示顶部对象。 如果这是您打算使用的,请阅读 CMYK 部分。

  2. 如果您的文档用于屏幕输出,并且您使用 RGB 颜色, 则可以设置 alpha 值,其中 alpha 是颜色不透明度的值。 默认 alpha 值为 1(完全不透明),您可以使用 0-1 范围内的任意实数值。

Alpha 透明度(alpha)类似于叠印,但在 RGB 颜色空间中工作。 下面的示例演示了 alpha 功能。请参考我们的网站 http://www.reportlab.com/snippets/ 并查找 overPrint 和 alpha 的代码片段, 以查看生成下图所示的代码。

def alpha(canvas):
    from reportlab.graphics.shapes import Rect
    from reportlab.lib.colors import Color, black, blue, red
    red50transparent = Color( 100, 0, 0, alpha=0.5)
    c = canvas 
    c.setFillColor(black)
    c.setFont('Helvetica', 10)
    c.drawString(25,180, 'solid')
    c.setFillColor(blue)
    c.rect(25,25,100,100, fill=True, stroke=False)
    c.setFillColor(red)
    c.rect(100,75,100,100, fill=True, stroke=False)
    c.setFillColor(black)
    c.drawString(225,180, 'transparent')
    c.setFillColor(blue)
    c.rect(225,25,100,100, fill=True, stroke=False)
    c.setFillColor(red50transparent)
    c.rect(300,75,100,100, fill=True, stroke=False)

CMYK 颜色

CMYK 即减色方法遵循打印机混合三种颜料(青色、品红和黄色)来形成颜色的方式。 由于混合化学品比组合光更困难,因此增加了第四个参数用于控制明暗。 例如,CMY 颜料的化学组合通常无法产生完美的黑色—— 而是产生一种浑浊的颜色——因此,为了获得黑色,打印机不使用 CMY 颜料, 而是使用直接的黑色油墨。由于 CMYK 更直接地映射到打印机硬件的工作方式, 以 CMYK 指定的颜色在打印时可能会提供更好的保真度和更好的控制。

CMYK 颜色有两种表示方式:每种颜色可以用 0 到 1 之间的实数值表示, 也可以用 0 到 100 之间的整数值表示。根据您的偏好, 您可以使用 CMYKColor(用于实数值)或 PCMYKColor(用于整数值)。 0 表示"无油墨",因此在白纸上打印得到白色。1(如果使用 PCMYKColor 则为 100) 表示"最大油墨量"。例如,CMYKColor(0,0,0,1) 是黑色, CMYKColor(0,0,0,0) 表示"无油墨",CMYKColor(0.5,0,0,0) 表示 50% 的青色。

def colorsCMYK(canvas):
    from reportlab.lib.colors import CMYKColor, PCMYKColor
    from reportlab.lib.units import inch
    # creates a black CMYK ; CMYKColor use real values
    black = CMYKColor(0,0,0,1)
    # creates a cyan CMYK ; PCMYKColor use integer values
    cyan = PCMYKColor(100,0,0,0)
    y = x = 0; dy=inch*3/4.0; dx=inch*5.5/5; w=h=dy/2; rdx=(dx-w)/2
    rdy=h/5.0; texty=h+2*rdy
    canvas.setFont("Helvetica",10)
    y = y + dy; x = 0
    for cmyk in [(1,0,0,0), (0,1,0,0), (0,0,1,0), (0,0,0,1), (0,0,0,0)]:
        c,m,y1,k = cmyk
        canvas.setFillColorCMYK(c,m,y1,k)
        canvas.rect(x+rdx, y+rdy, w, h, fill=1)
        canvas.setFillColor(black)
        canvas.drawCentredString(x+dx/2, y+texty, "c%s m%s y%s k%s"%cmyk)
        x = x+dx

2.9 颜色空间检查

画布的 enforceColorSpace 参数用于强制文档中使用的颜色模型的一致性。 它接受以下值:CMYK、RGB、SEP、SEP_BLACK、SEP_CMYK。 'SEP' 指命名颜色分色,例如 Pantone 专色——这些可以根据使用的参数 与 CMYK 或 RGB 混合。默认值为 'MIXED',允许您使用任何颜色空间的颜色。 如果使用的任何颜色无法转换为指定的模型(例如 rgb 和 cmyk),则会引发异常 (更多信息请参见 test_pdfgen_general)。此方法不检查文档中包含的外部图像。

2.10 颜色叠印

当两个 CMYK 着色对象在印刷中重叠时,要么"位于上方"的对象 会挖空其下方对象的颜色,要么两个对象的颜色将在重叠区域混合。 此行为可以使用属性 overPrint 来设置。

overPrint 函数将使重叠区域的颜色混合。在下面的示例中, 左侧矩形的颜色在重叠处应该出现混合——如果您看不到此效果, 可能需要在 PDF 查看软件中启用"叠印预览"选项。 某些 PDF 查看器(如 evince)不支持叠印;但 Adobe Acrobat Reader 支持它。

其他对象打印顺序示例

单词"SPUMONI"以白色绘制在彩色矩形之上, 产生了一种"移除"单词内部颜色的视觉效果。

def spumoni(canvas):
    from reportlab.lib.units import inch
    from reportlab.lib.colors import pink, green, brown, white
    x = 0; dx = 0.4*inch
    for i in range(4):
        for color in (pink, green, brown):
            canvas.setFillColor(color)
            canvas.rect(x,0,dx,3*inch,stroke=0,fill=1)
            x = x+dx
    canvas.setFillColor(white)
    canvas.setStrokeColor(white)
    canvas.setFont("Helvetica-Bold", 85)
    canvas.drawCentredString(2.75*inch, 1.3*inch, "SPUMONI")

单词的最后几个字母不可见,因为默认的 canvas 背景是白色的, 在白色背景上绘制白色字母不会产生可见效果。

这种逐层构建复杂绘制的方法可以在 pdfgen 中使用非常多层—— 物理上的限制比处理真实颜料时要少得多。

def spumoni2(canvas):
    from reportlab.lib.units import inch
    from reportlab.lib.colors import pink, green, brown, white, black
    # draw the previous drawing
    spumoni(canvas)
    # now put an ice cream cone on top of it:
    # first draw a triangle (ice cream cone)
    p = canvas.beginPath()
    xcenter = 2.75*inch
    radius = 0.45*inch
    p.moveTo(xcenter-radius, 1.5*inch)
    p.lineTo(xcenter+radius, 1.5*inch)
    p.lineTo(xcenter, 0)
    canvas.setFillColor(brown)
    canvas.setStrokeColor(black)
    canvas.drawPath(p, fill=1)
    # draw some circles (scoops)
    y = 1.5*inch
    for color in (pink, green, brown):
        canvas.setFillColor(color)
        canvas.circle(xcenter, y, radius, fill=1)
        y = y+radius

spumoni2 函数在 spumoni 绘制之上叠加了一个冰淇淋甜筒。 请注意,甜筒和冰淇淋球的不同部分也相互叠加。

2.11 标准字体和文本对象

pdfgen 中可以以许多不同的颜色、字体和大小绘制文本。 textsize 函数演示了如何更改文本的颜色、字体和大小, 以及如何在页面上放置文本。

def textsize(canvas):
    from reportlab.lib.units import inch
    from reportlab.lib.colors import magenta, red
    canvas.setFont("Times-Roman", 20)
    canvas.setFillColor(red)
    canvas.drawCentredString(2.75*inch, 2.5*inch, "Font size examples")
    canvas.setFillColor(magenta)
    size = 7
    y = 2.3*inch
    x = 1.3*inch
    for line in lyrics:
        canvas.setFont("Helvetica", size)
        canvas.drawRightString(x,y,"%s points: " % size)
        canvas.drawString(x,y, line)
        y = y-size*1.2
        size = size+1.5

textsize 函数生成以下页面。

pdfgen 中始终有多种字体可用。

def fonts(canvas):
    from reportlab.lib.units import inch
    text = "Now is the time for all good men to..."
    x = 1.8*inch
    y = 2.7*inch
    for font in canvas.getAvailableFonts():
        canvas.setFont(font, 10)
        canvas.drawString(x,y,text)
        canvas.setFont("Helvetica", 10)
        canvas.drawRightString(x-10,y, font+":")
        y = y-13

fonts 函数列出了始终可用的字体。这些字体不需要存储在 PDF 文档中, 因为 Acrobat Reader 中保证包含它们。

Symbol 和 ZapfDingbats 字体无法正确显示,因为所需的字形不存在于这些字体中。

有关如何使用任意字体的信息,请参见下一章。

2.12 文本对象方法

要在 PDF 文档中专门呈现文本,请使用文本对象。 文本对象接口提供了在 canvas 级别不直接可用的文本布局参数的详细控制。 此外,它生成的 PDF 更小,渲染速度比许多单独调用 drawString 方法更快。

textobject.setTextOrigin(x,y)
textobject.setTextTransform(a,b,c,d,e,f)
textobject.moveCursor(dx, dy) # from start of current LINE
(x,y) = textobject.getCursor()
x = textobject.getX(); y = textobject.getY()
textobject.setFont(psfontname, size, leading = None)
textobject.textOut(text)
textobject.textLine(text='')
textobject.textLines(stuff, trim=1)

上面显示的文本对象方法与基本文本几何相关。

文本对象维护一个文本光标,在绘制文本时它在页面上移动。 例如,setTextOrigin 将光标放置在已知位置, textLinetextLines 方法将文本光标向下移动到已绘制的行之后。

def cursormoves1(canvas):
    from reportlab.lib.units import inch
    textobject = canvas.beginText()
    textobject.setTextOrigin(inch, 2.5*inch)
    textobject.setFont("Helvetica-Oblique", 14)
    for line in lyrics:
        textobject.textLine(line)
    textobject.setFillGray(0.4)
    canvas.drawText(textobject)

cursormoves 函数依赖于设置原点后文本光标的自动移动来放置文本。

也可以通过使用 moveCursor 方法(它将光标从当前的起始位置 偏移移动,而不是从当前光标位置移动,并且正向的 y 偏移 是向移动(这与正常几何中正向 y 通常向上移动相反) 来更明确地控制光标的移动。

def cursormoves2(canvas):
    from reportlab.lib.units import inch
    textobject = canvas.beginText()
    textobject.setTextOrigin(2, 2.5*inch)
    textobject.setFont("Helvetica-Oblique", 14)
    for line in lyrics:
        textobject.textOut(line)
        textobject.moveCursor(14,14) # POSITIVE Y moves down!!!
    textobject.setFillColorRGB(0.4,0,1)
    canvas.drawText(textobject)

这里 textOut 不会向下移动一行,而 textLine 函数会向下移动。

字符间距

textobject.setCharSpace(charSpace)

setCharSpace 方法调整文本的一个参数——字符间距。

def charspace(canvas):
    from reportlab.lib.units import inch
    textobject = canvas.beginText()
    textobject.setTextOrigin(3, 2.5*inch)
    textobject.setFont("Helvetica-Oblique", 10)
    charspace = 0
    for line in lyrics:
        textobject.setCharSpace(charspace)
        textobject.textLine("%s: %s" %(charspace,line))
        charspace = charspace+0.5
    textobject.setFillGray(0.4)
    canvas.drawText(textobject)

charspace 函数演示了各种间距设置。 它生成以下页面。

单词间距

textobject.setWordSpace(wordSpace)

setWordSpace 方法调整单词之间的间距。

def wordspace(canvas):
    from reportlab.lib.units import inch
    textobject = canvas.beginText()
    textobject.setTextOrigin(3, 2.5*inch)
    textobject.setFont("Helvetica-Oblique", 12)
    wordspace = 0
    for line in lyrics:
        textobject.setWordSpace(wordspace)
        textobject.textLine("%s: %s" %(wordspace,line))
        wordspace = wordspace+2.5
    textobject.setFillColorCMYK(0.4,0,0.4,0.2)
    canvas.drawText(textobject)

wordspace 函数展示了不同单词间距设置的效果如下。

水平缩放

textobject.setHorizScale(horizScale)

文本行可以通过 setHorizScale 方法在水平方向上拉伸或缩小。

def horizontalscale(canvas):
    from reportlab.lib.units import inch
    textobject = canvas.beginText()
    textobject.setTextOrigin(3, 2.5*inch)
    textobject.setFont("Helvetica-Oblique", 12)
    horizontalscale = 80 # 100 is default
    for line in lyrics:
        textobject.setHorizScale(horizontalscale)
        textobject.textLine("%s: %s" %(horizontalscale,line))
        horizontalscale = horizontalscale+10
    textobject.setFillColorCMYK(0.0,0.4,0.4,0.2)
    canvas.drawText(textobject)

水平缩放参数 horizScale 以百分比给出(默认值为 100), 因此下面显示的 80 设置看起来较窄。

行间距(Leading)

textobject.setLeading(leading)

一行文本起始位置到下一行起始位置之间的垂直偏移量称为行距(leading)偏移。 setLeading 方法调整行距偏移。

def leading(canvas):
    from reportlab.lib.units import inch
    textobject = canvas.beginText()
    textobject.setTextOrigin(3, 2.5*inch)
    textobject.setFont("Helvetica-Oblique", 14)
    leading = 8
    for line in lyrics:
        textobject.setLeading(leading)
        textobject.textLine("%s: %s" %(leading,line))
        leading = leading+2.5
    textobject.setFillColorCMYK(0.8,0,0,0.3)
    canvas.drawText(textobject)

如下所示,如果行距偏移设置得太小,一行的字符可能会覆盖 上一行字符的底部部分。

其他文本对象方法

textobject.setTextRenderMode(mode)

setTextRenderMode 方法允许将文本用作前景来裁剪背景绘制等。

textobject.setRise(rise)

setRise 方法将文本上移下移 (例如用于创建上标或下标)。

textobject.setFillColor(aColor);
textobject.setStrokeColor(self, aColor)
# and similar

这些颜色更改操作更改文本的 颜色, 其他方面与 canvas 对象的颜色方法类似。

2.13 路径与线条

正如文本对象专为文本的专门呈现而设计, 路径对象专为图形的专门构建而设计。当路径对象被绘制到 canvas 上时, 它们作为一个整体图形(如矩形)被绘制,整个图形的绘制模式可以调整: 图形的线条可以被绘制(描边)或不绘制;图形的内部可以被填充或不填充;等等。

例如,star 函数使用路径对象来绘制一颗星

def star(canvas, title="Title Here", aka="Comment here.",
         xcenter=None, ycenter=None, nvertices=5):
    from math import pi
    from reportlab.lib.units import inch
    radius=inch/3.0
    if xcenter is None: xcenter=2.75*inch
    if ycenter is None: ycenter=1.5*inch
    canvas.drawCentredString(xcenter, ycenter+1.3*radius, title)
    canvas.drawCentredString(xcenter, ycenter-1.4*radius, aka)
    p = canvas.beginPath()
    p.moveTo(xcenter,ycenter+radius)
    from math import pi, cos, sin
    angle = (2*pi)*2/5.0
    startangle = pi/2.0
    for vertex in range(nvertices-1):
        nextangle = angle*(vertex+1)+startangle
        x = xcenter + radius*cos(nextangle)
        y = ycenter + radius*sin(nextangle)
        p.lineTo(x,y)
    if nvertices==5:
       p.close()
    canvas.drawPath(p)

star 函数的设计旨在用于说明 pdfgen 支持的各种线条样式参数。

线条连接设置

setLineJoin 方法可以调整线段相交时是形成尖角、方角还是圆角。

def joins(canvas):
    from reportlab.lib.units import inch
    # make lines big
    canvas.setLineWidth(5)
    star(canvas, "Default: mitered join", "0: pointed", xcenter = 1*inch)
    canvas.setLineJoin(1)
    star(canvas, "Round join", "1: rounded")
    canvas.setLineJoin(2)
    star(canvas, "Bevelled join", "2: square", xcenter=4.5*inch)

线条连接设置对于粗线才真正有意义,因为对于细线来说效果不明显。

线条端点设置

线条端点设置通过 setLineCap 方法调整,决定终止线端的形状—— 是在顶点处精确的方形、超出顶点的方形,还是超出顶点的半圆形。

def caps(canvas):
    from reportlab.lib.units import inch
    # make lines big
    canvas.setLineWidth(5)
    star(canvas, "Default", "no projection",xcenter = 1*inch,
         nvertices=4)
    canvas.setLineCap(1)
    star(canvas, "Round cap", "1: ends in half circle", nvertices=4)
    canvas.setLineCap(2)
    star(canvas, "Square cap", "2: projects out half a width", xcenter=4.5*inch,
       nvertices=4)

线条端点设置与线条连接设置一样,只有在线条较粗时才能清楚地看到。

虚线和断续线

setDash 方法允许将线条断开为点或虚线。

def dashes(canvas):
    from reportlab.lib.units import inch
    # make lines big
    canvas.setDash(6,3)
    star(canvas, "Simple dashes", "6 points on, 3 off", xcenter = 1*inch)
    canvas.setDash(1,2)
    star(canvas, "Dots", "One on, two off")
    canvas.setDash([1,1,3,3,1,4,4,1], 0)
    star(canvas, "Complex Pattern", "[1,1,3,3,1,4,4,1]", xcenter=4.5*inch)

虚线或点的模式可以是简单的开/关重复模式,也可以是复杂的重复模式。

使用路径对象创建复杂图形

线段、曲线、弧线和其他图形的组合可以使用路径对象合并为单个图形。 例如,下面显示的函数使用线段和曲线构建了两个路径对象。 该函数稍后将用作铅笔图标构建的一部分。

def penciltip(canvas, debug=1):
    from reportlab.lib.colors import tan, black, green
    from reportlab.lib.units import inch
    u = inch/10.0
    canvas.setLineWidth(4)
    if debug:
        canvas.scale(2.8,2.8) # make it big
        canvas.setLineWidth(1) # small lines
    canvas.setStrokeColor(black)
    canvas.setFillColor(tan)
    p = canvas.beginPath()
    p.moveTo(10*u,0)
    p.lineTo(0,5*u)
    p.lineTo(10*u,10*u)
    p.curveTo(11.5*u,10*u, 11.5*u,7.5*u, 10*u,7.5*u)
    p.curveTo(12*u,7.5*u, 11*u,2.5*u, 9.7*u,2.5*u)
    p.curveTo(10.5*u,2.5*u, 11*u,0, 10*u,0)
    canvas.drawPath(p, stroke=1, fill=1)
    canvas.setFillColor(black)
    p = canvas.beginPath()
    p.moveTo(0,5*u)
    p.lineTo(4*u,3*u)
    p.lineTo(5*u,4.5*u)
    p.lineTo(3*u,6.5*u)
    canvas.drawPath(p, stroke=1, fill=1)
    if debug:
        canvas.setStrokeColor(green) # put in a frame of reference
        canvas.grid([0,5*u,10*u,15*u], [0,5*u,10*u])

请注意,铅笔尖的内部作为一个对象填充,即使它是由多条线段和曲线构成的。 铅笔芯然后使用新的路径对象绘制在其上方。

2.14 矩形、圆形、椭圆

pdfgen 模块支持许多常用的形状,如矩形、圆角矩形、椭圆和圆形。 这些图形中的每一种都可以在路径对象中使用,也可以直接绘制在 canvas 上。 例如,下面的 pencil 函数使用矩形和圆角矩形以及各种填充颜色 和一些其他标注来绘制铅笔图标。

def pencil(canvas, text="No.2"):
    from reportlab.lib.colors import yellow, red, black,white
    from reportlab.lib.units import inch
    u = inch/10.0
    canvas.setStrokeColor(black)
    canvas.setLineWidth(4)
    # draw erasor
    canvas.setFillColor(red)
    canvas.circle(30*u, 5*u, 5*u, stroke=1, fill=1)
    # draw all else but the tip (mainly rectangles with different fills)
    canvas.setFillColor(yellow)
    canvas.rect(10*u,0,20*u,10*u, stroke=1, fill=1)
    canvas.setFillColor(black)
    canvas.rect(23*u,0,8*u,10*u,fill=1)
    canvas.roundRect(14*u, 3.5*u, 8*u, 3*u, 1.5*u, stroke=1, fill=1)
    canvas.setFillColor(white)
    canvas.rect(25*u,u,1.2*u,8*u, fill=1,stroke=0)
    canvas.rect(27.5*u,u,1.2*u,8*u, fill=1, stroke=0)
    canvas.setFont("Times-Roman", 3*u)
    canvas.drawCentredString(18*u, 4*u, text)
    # now draw the tip
    penciltip(canvas,debug=0)
    # draw broken lines across the body.
    canvas.setDash([10,5,16,10],0)
    canvas.line(11*u,2.5*u,22*u,2.5*u)
    canvas.line(22*u,7.5*u,12*u,7.5*u)

请注意,此函数用于创建左侧的"边栏铅笔"。 还要注意元素的绘制顺序很重要,因为例如白色矩形会"擦除"黑色矩形的某些部分, 而"笔尖"会覆盖黄色矩形的某些部分。

2.15 贝塞尔曲线

希望构建曲线边界的程序通常使用贝塞尔曲线来形成边界。

def bezier(canvas):
    from reportlab.lib.colors import yellow, green, red, black
    from reportlab.lib.units import inch
    i = inch
    d = i/4
    # define the bezier curve control points
    x1,y1, x2,y2, x3,y3, x4,y4 = d,1.5*i, 1.5*i,d, 3*i,d, 5.5*i-d,3*i-d
    # draw a figure enclosing the control points
    canvas.setFillColor(yellow)
    p = canvas.beginPath()
    p.moveTo(x1,y1)
    for (x,y) in [(x2,y2), (x3,y3), (x4,y4)]:
        p.lineTo(x,y)
    canvas.drawPath(p, fill=1, stroke=0)
    # draw the tangent lines
    canvas.setLineWidth(inch*0.1)
    canvas.setStrokeColor(green)
    canvas.line(x1,y1,x2,y2)
    canvas.setStrokeColor(red)
    canvas.line(x3,y3,x4,y4)
    # finally draw the curve
    canvas.setStrokeColor(black)
    canvas.bezier(x1,y1, x2,y2, x3,y3, x4,y4)

贝塞尔曲线由四个控制点 (x1,y1)(x2,y2)(x3,y3)(x4,y4) 指定。 曲线从 (x1,y1) 开始,在 (x4,y4) 结束, 从 (x1,y1)(x2,y2) 的线段和从 (x3,y3)(x4,y4) 的线段 都构成曲线的切线。此外,曲线完全包含在以控制点为顶点的凸多边形中。

上面的绘制(testbezier 的输出)展示了贝塞尔曲线、 由控制点定义的切线以及以控制点为顶点的凸多边形。

平滑连接贝塞尔曲线序列

将多条贝塞尔曲线连接起来形成一条平滑曲线通常很有用。 要从多条贝塞尔曲线构建更大的平滑曲线, 请确保在控制点处相连的相邻贝塞尔曲线的切线位于同一条直线上。

def bezier2(canvas):
    from reportlab.lib.colors import yellow, green, red, black
    from reportlab.lib.units import inch
    # make a sequence of control points
    xd,yd = 5.5*inch/2, 3*inch/2
    xc,yc = xd,yd
    dxdy = [(0,0.33), (0.33,0.33), (0.75,1), (0.875,0.875),
            (0.875,0.875), (1,0.75), (0.33,0.33), (0.33,0)]
    pointlist = []
    for xoffset in (1,-1):
        yoffset = xoffset
        for (dx,dy) in dxdy:
            px = xc + xd*xoffset*dx
            py = yc + yd*yoffset*dy
            pointlist.append((px,py))
        yoffset = -xoffset
        for (dy,dx) in dxdy:
            px = xc + xd*xoffset*dx
            py = yc + yd*yoffset*dy
            pointlist.append((px,py))
    # draw tangent lines and curves
    canvas.setLineWidth(inch*0.1)
    while pointlist:
        [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] = pointlist[:4]
        del pointlist[:4]
        canvas.setLineWidth(inch*0.1)
        canvas.setStrokeColor(green)
        canvas.line(x1,y1,x2,y2)
        canvas.setStrokeColor(red)
        canvas.line(x3,y3,x4,y4)
        # finally draw the curve
        canvas.setStrokeColor(black)
        canvas.bezier(x1,y1, x2,y2, x3,y3, x4,y4)

testbezier2 创建的图形描述了一条平滑的复杂曲线, 因为相邻的切线"对齐"了,如下所示。

2.16 路径对象方法

路径对象通过在画布上的起始点放置"画笔"或"画刷" 并绘制线段或曲线到画布上的其他点来构建复杂图形。 大多数操作从上一次操作的终点开始在画布上绘制, 并将画刷留在新的终点。

pathobject.moveTo(x,y)

moveTo 方法抬起画刷(结束当前正在进行的任何线段或曲线序列), 并将画刷放在画布上的新 (x,y) 位置,以开始新的路径序列。

pathobject.lineTo(x,y)

lineTo 方法从当前画刷位置到新 (x,y) 位置绘制直线段。

pathobject.curveTo(x1, y1, x2, y2, x3, y3) 

curveTo 方法从当前画刷位置开始绘制贝塞尔曲线, 使用 (x1,y1)(x2,y2)(x3,y3) 作为其他三个控制点, 将画刷留在 (x3,y3)

pathobject.arc(x1,y1, x2,y2, startAng=0, extent=90) 
pathobject.arcTo(x1,y1, x2,y2, startAng=0, extent=90) 

arcarcTo 方法绘制部分椭圆。arc 方法首先"抬起画刷" 并开始新的形状序列。arcTo 方法在绘制部分椭圆之前, 通过线段将部分椭圆的起点连接到当前形状序列。 点 (x1,y1)(x2,y2) 定义包围椭圆的矩形的对角点。 startAng 是一个角度(以度为单位),指定从哪里开始部分椭圆, 其中 0 角度是包围矩形右边的中点 (当 (x1,y1) 是左下角且 (x2,y2) 是右上角时)。 extent 是在椭圆上遍历的角度(以度为单位)。

def arcs(canvas):
    from reportlab.lib.units import inch
    canvas.setLineWidth(4)
    canvas.setStrokeColorRGB(0.8, 1, 0.6)
    # draw rectangles enclosing the arcs
    canvas.rect(inch, inch, 1.5*inch, inch)
    canvas.rect(3*inch, inch, inch, 1.5*inch)
    canvas.setStrokeColorRGB(0, 0.2, 0.4)
    canvas.setFillColorRGB(1, 0.6, 0.8)
    p = canvas.beginPath()
    p.moveTo(0.2*inch, 0.2*inch)
    p.arcTo(inch, inch, 2.5*inch,2*inch, startAng=-30, extent=135)
    p.arc(3*inch, inch, 4*inch, 2.5*inch, startAng=-45, extent=270)
    canvas.drawPath(p, fill=1, stroke=1)

上面的 arcs 函数演示了两种部分椭圆方法。 它产生以下绘制。

pathobject.rect(x, y, width, height) 

rect 方法绘制一个矩形,左下角位于 (x,y), 具有指定的 widthheight

pathobject.ellipse(x, y, width, height)

ellipse 方法绘制一个椭圆,包含在左下角位于 (x,y)、 具有指定 widthheight 的矩形中。

pathobject.circle(x_cen, y_cen, r) 

circle 方法绘制一个以 (x_cen, y_cen) 为圆心、 以 r 为半径的圆。

def variousshapes(canvas):
    from reportlab.lib.units import inch
    inch = int(inch)
    canvas.setStrokeGray(0.5)
    canvas.grid(range(0,int(11*inch/2),int(inch/2)), range(0,int(7*inch/2),int(inch/2)))
    canvas.setLineWidth(4)
    canvas.setStrokeColorRGB(0, 0.2, 0.7)
    canvas.setFillColorRGB(1, 0.6, 0.8)
    p = canvas.beginPath()
    p.rect(0.5*inch, 0.5*inch, 0.5*inch, 2*inch)
    p.circle(2.75*inch, 1.5*inch, 0.3*inch)
    p.ellipse(3.5*inch, 0.5*inch, 1.2*inch, 2*inch)
    canvas.drawPath(p, fill=1, stroke=1)

上面的 variousshapes 函数展示了放置在参考网格中的矩形、圆形和椭圆。

pathobject.close() 

close 方法通过从图形的最后一个点到图形的起始点 (即画刷最近一次通过 moveToarc 或其他放置操作放置在纸上的点) 绘制线段来关闭当前图形。

def closingfigures(canvas):
    from reportlab.lib.units import inch
    h = inch/3.0; k = inch/2.0
    canvas.setStrokeColorRGB(0.2,0.3,0.5)
    canvas.setFillColorRGB(0.8,0.6,0.2)
    canvas.setLineWidth(4)
    p = canvas.beginPath()
    for i in (1,2,3,4):
        for j in (1,2):
            xc,yc = inch*i, inch*j
            p.moveTo(xc,yc)
            p.arcTo(xc-h, yc-k, xc+h, yc+k, startAng=0, extent=60*i)
            # close only the first one, not the second one
            if j==1:
                p.close()
    canvas.drawPath(p, fill=1, stroke=1)

closingfigures 函数演示了关闭或不关闭图形的效果, 包括线段和部分椭圆。

关闭或不关闭图形只影响图形的描边轮廓,不影响图形的填充,如上所示。

有关使用路径对象进行更广泛绘制的示例,请查看 hand 函数。

def hand(canvas, debug=1, fill=0):
    (startx, starty) = (0,0)
    curves = [
      ( 0, 2), ( 0, 4), ( 0, 8), # back of hand
      ( 5, 8), ( 7,10), ( 7,14),
      (10,14), (10,13), ( 7.5, 8), # thumb
      (13, 8), (14, 8), (17, 8),
      (19, 8), (19, 6), (17, 6),
      (15, 6), (13, 6), (11, 6), # index, pointing
      (12, 6), (13, 6), (14, 6),
      (16, 6), (16, 4), (14, 4),
      (13, 4), (12, 4), (11, 4), # middle
      (11.5, 4), (12, 4), (13, 4),
      (15, 4), (15, 2), (13, 2),
      (12.5, 2), (11.5, 2), (11, 2), # ring
      (11.5, 2), (12, 2), (12.5, 2),
      (14, 2), (14, 0), (12.5, 0),
      (10, 0), (8, 0), (6, 0), # pinky, then close
      ]
    from reportlab.lib.units import inch
    if debug: canvas.setLineWidth(6)
    u = inch*0.2
    p = canvas.beginPath()
    p.moveTo(startx, starty)
    ccopy = list(curves)
    while ccopy:
        [(x1,y1), (x2,y2), (x3,y3)] = ccopy[:3]
        del ccopy[:3]
        p.curveTo(x1*u,y1*u,x2*u,y2*u,x3*u,y3*u)
    p.close()
    canvas.drawPath(p, fill=fill)
    if debug:
        from reportlab.lib.colors import red, green
        (lastx, lasty) = (startx, starty)
        ccopy = list(curves)
        while ccopy:
            [(x1,y1), (x2,y2), (x3,y3)] = ccopy[:3]
            del ccopy[:3]
            canvas.setStrokeColor(red)
            canvas.line(lastx*u,lasty*u, x1*u,y1*u)
            canvas.setStrokeColor(green)
            canvas.line(x2*u,y2*u, x3*u,y3*u)
            (lastx,lasty) = (x3,y3)

在调试模式下(默认),hand 函数显示了用于构建图形的贝塞尔曲线的切线段。 请注意,切线段对齐的地方曲线平滑连接,而切线段不对齐的地方曲线会出现"锐利边缘"。

在非调试模式下使用时,hand 函数只显示贝塞尔曲线。 设置 fill 参数后,图形使用当前填充颜色进行填充。

def hand2(canvas):
    canvas.translate(20,10)
    canvas.setLineWidth(3)
    canvas.setFillColorRGB(0.1, 0.3, 0.9)
    canvas.setStrokeGray(0.5)
    hand(canvas, debug=0, fill=1)

请注意,边框的"描边"在重叠处会绘制在内部填充之上。

2.17 扩展阅读:ReportLab 图形库

到目前为止,我们看到的图形都是在相当低的层次上创建的。 但值得注意的是,还有另一种方式可以使用专用的 高级 ReportLab 图形库 创建更复杂的图形。

它可以用于生成高质量、跨平台、可复用的图形, 支持不同的输出格式(矢量和位图),如 PDF、EPS、SVG、JPG 和 PNG。

关于其理念和功能的更详细描述将在本文档的第 11 章 图形 中介绍, 其中包含有关现有组件以及如何创建自定义组件的信息。

第 11 章还包含有关 ReportLab 图表包及其组件(标签、轴、图例 和不同类型的图表,如条形图、折线图和饼图)的详细信息, 这些直接构建在图形库之上。