Reportlab生成PDF文件

第三方模块reportlab的技术文档里有一句话,大意是生产pdf文档时重复代码的都应该做成form,可以节省很多时间和空间。为啥重复的部分要写成form不写成函数呢?

这里的form不翻译成表格或表单,form还有形式、形状、形态……的意思,但用哪个都不太贴切,所以我们不用中文翻译还是用form这个名称。

首先来看怎样生成一个form,在主函数里生成画布对象c,然后调用forms()来看form的生成和使用:

if __name__ == '__main__':
    from reportlab.pdfgen import canvas
    c = canvas.Canvas('speCap.pdf')
    forms(c)
    c.showPage()
    c.save()

在函数forms()里,生成form用画布对象的方法函数beginForm(),用给form起的名字字串做实参。然后放重复使用的代码,画布对象的方法函数endForm()宣告form定义的完成。

def forms(myCanvas):
    # ------------------ 生成Form ---------------------
    myCanvas.beginForm("SpumoniForm")
    # form的名字为SpumoniForm
    spumoni(myCanvas)
    # 调用函数画图一所示的图形
    from reportlab.lib.colors import red, green, blue
    myCanvas.setFillColor(green)  
    # 设置字体颜色
    myCanvas.setFont('Helvetica-BoldOblique', 20)
    # 设置字体和大小
    myCanvas.drawString(80, 260, 'Love, Enjoy, Appreciate')
    # 写字
    myCanvas.setStrokeColor(blue)
    # 设置线条颜色
    myCanvas.rect(50, 50, 300, 300)
    # 画正方形
    myCanvas.endForm()

spumoni()是前面定义过的、往画布上画图形的函数,调用它可生成如图一这样的图形:

图一

定义SpumoniForm这个form所做的事情就是:画图一的图形;设置字体和大小,而后写一行字Love, Enjoy, Appreciate;再画一个蓝色的正方形,如图二

图二

但现在我们只是定义了这个form,没有使用它,只定义不使用的form是不会被显示的。

用画布对象的方法函数doForm(form的名字)来使用form,所以接下来:

def forms(myCanvas):
    ……
    # -------------- 使用Form ---------------------
    myCanvas.setStrokeColor(red)  
    # 设置线条颜色为红色
    myCanvas.doForm("SpumoniForm")

这时候生成的整个pdf文件打开后看到的图形和文字就是图二的样子。注意在用doForm()应用SpumoniForm之前整个画布的线条颜色用 myCanvas.setStrokeColor(red)设成了红色,而画布上显示的是蓝色正方形,可见画布上的设置没有影响到form内部的操作。虽然form作为一个整体往画布上放时还是要受制于画布的。

应用SpumoniForm之后,继续往画布上画圆圈和写字:

def forms(myCanvas):
    ……  
    # -------------- 使用Form ---------------------
    myCanvas.setStrokeColor(red)
    myCanvas.doForm("SpumoniForm")

    # 圆和字的颜色由画布规定
    myCanvas.circle(260, 330, 25)
    myCanvas.setFont('Times-Italic', 16)
    myCanvas.drawCentredString(260, 330, 'fly')

生成的speCap.pdf文件打开后,forms(c)函数起作用的部分效果如图三:

图三

圆圈的颜色是红色,圆圈里的字fly是黑色(之前没有对画布上字体颜色的设置,默认是黑色),没有受SpumoniForm内部绿字蓝线条的影响。

现在把SpumoniForm里的操作用一个函数spuFunc()来实现,就是把myCanvas.beginForm("SpumoniForm")与myCanvas.endForm()之间的语句全盘搬过去:

def spuFunc(c):
    spumoni(c)
    c.setFillColor(green)
    c.setFont('Helvetica-BoldOblique', 20)
    c.drawString(80, 260, 'Love, Enjoy, Appreciate')
    c.setStrokeColor(blue)
    c.rect(50, 50, 300, 300)

然后把函数forms()中的语句myCanvas.doForm("SpumoniForm")替换成spuFunc(myCanvas),其它保持不变:

def forms(myCanvas):
    ……
    myCanvas.setStrokeColor(red)
    # myCanvas.doForm("SpumoniForm")

    # 用函数实现Form同样的功能
    spuFunc(myCanvas)
    
    myCanvas.circle(260, 330, 25)
    myCanvas.setFont('Times-Italic', 16)
    myCanvas.drawCentredString(260, 330, 'fly')

运行结果如图四:

图四

首先函数调用spuFunc(myCanvas)把myCanvas作为实参传给了函数spuFunc()的形参,跟数值和字符串实参形参不一样,那种情况传递的是值,形参的作用域限于所在的函数内部。这里传递的是画布的地址,也就是说实参myCanvas和形参c指向同一块画布,它俩是同一个画布对象的不同名字,不受局部作用域的限制。所以在Forms()函数里用myCanvas.setStrokeColor(red)将画布的线条线条设成红色,进入函数spuFunc()也是有效的,只不过后来被spuFunc()函数内的语句c.setStrokeColor(blue)給覆盖了,字体、字体大小和字的颜色同理。这就是为什么画出的正方形是蓝色,写出来的字love, enjoy and appreciate是绿色的。从spuFun()函数出来回到forms()函数时,同一个画布对象的字体颜色仍是绿色,线条颜色仍是蓝色,所以写出来的字fly是绿色的,画出来的圈则是蓝色。

可见如果用函数,函数内的画布设置受调用之前的画布设置影响,函数内部的画布设置也会影响调用之后的画布设置;而用form实现跟函数同样的功能,form内部的操作比如画布设置相对于form外是独立互不影响的。

如果非要用函数实现form的功能,那就在进出函数时用方法函数saveState()和restoreState()保存和恢复画布的状态。


全部代码:

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

def spuFunc(c):
    spumoni(c)
    c.setFillColor(green)
    c.setFont('Helvetica-BoldOblique', 20)
    c.drawString(80, 260, 'Love, Enjoy, Appreciate')
    c.setStrokeColor(blue)
    c.rect(50, 50, 300, 300)

def forms(myCanvas):
    # ------------------ 生成Form ---------------------
    myCanvas.beginForm("SpumoniForm")
    # form的名字为SpumoniForm
    spumoni(myCanvas)
    # 调用函数画图一所示的图形
    from reportlab.lib.colors import red, green, blue
    myCanvas.setFillColor(green)  
    # 设置字体颜色
    myCanvas.setFont('Helvetica-BoldOblique', 20)
    # 设置字体和大小
    myCanvas.drawString(80, 260, 'Love, Enjoy, Appreciate')
    # 写字
    myCanvas.setStrokeColor(blue)
    # 设置线条颜色
    myCanvas.rect(50, 50, 300, 300)
    # 画正方形

    # -------------- 使用Form ---------------------
    myCanvas.setStrokeColor(red)
    myCanvas.doForm("SpumoniForm")

    # 用函数实现Form同样的功能
    # spuFunc(myCanvas)

    # 圆和字的颜色由画布规定
    myCanvas.circle(260, 330, 25)
    myCanvas.setFont('Times-Italic', 16)
    myCanvas.drawCentredString(260, 330, 'fly')

if __name__ == '__main__':
    from reportlab.pdfgen import canvas
    c = canvas.Canvas('speCap.pdf')
    forms(c)
    c.showPage()
    c.save()