Reportlab生成PDF文件
pdfgen是往空白画布上写文字、画图等;“鸭嘴兽”Platypus(Page Layout and Typography Using Scripts)则致力于把文档的布局和文档的内容尽可能地分开,段落设置用段落格式,页面设置用页面模版,更高层级一些。用Playpus生成上百个文档上千不同风格的页面可能只需要改动段落格式、页面布局的几个参数而已。

Platypus的设计从上倒下分几层:

    DocTemplates是最外层的容器

    PageTemplates参数规划页面布局

    Frame是页面的一个分区,承载flowables

   flowables是“流进“文档的段落(Paragraph)、图片(Image)、表格(Table)、空白(Spacer)、分页符(PageBreak)……

pdfgen的画布canvas作为最底层,接收这些高层做出来的东西。

文档模版的结构

那是不是每次用Platypus做版式设计都得这么周全都要这么层次分明呢?

不需要!

实际上我们最经常用得是从reportlab.platypus引入SimpleDocTemplate模版生成文档对象,往里灌各个flowables,然后用文档对象的方法函数build()把它们组合成一个pdf文件。flowable是个基类,衍生出Paragraph、Image、Table等对象。

frame可以直接跟pdfgen的画布canvas联手生成pdf。

flowable对象有自己的方法函数drawOn()画到canvas上,生成pdf文件,比如Paragraph.drawOn(canvas, x, y。,x,y是坐标,指定段落在画布上的放置位置。

我试图找到一个用足四个层级的例子,reportlab的技术文档语焉不详,好不容易在这里:http://code.activestate.com/recipes/123612-basedoctemplate-with-2-pagetemplate/找到一个,稍微改了一下放在这里。

我们常用的SimpleDocTemplate是从基类BaseDocTemplate衍生出来的,这个例子用BaseDocTemplate生成的文档版式包容两种页面版式PageTemplate,一种是我们最常用的单栏(整个页面是一个分区),另一个是双栏(页面分成左右两个分区)。分栏由Frame完成,Frame里容纳Paragraph和Spacer这些flowables。效果如下:

 

单栏页页脚与双栏页页脚亦不同:
  

代码如下:

#----- BaseDocTemplate带两个页面模版PageTemplate(单栏和双栏) -----

from reportlab.platypus import BaseDocTemplate, Frame, Paragraph, \
                 NextPageTemplate, PageBreak, PageTemplate
from reportlab.lib.units import inch
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet

# 注册要用的中文字体
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase import pdfmetrics
pdfmetrics.registerFont(TTFont('lively', '/Library/Fonts/Chinese/ChaoZiSheZengYuBoShouShuJian-2.ttf'))
pdfmetrics.registerFont(TTFont('apple', '/Library/Fonts/Chinese/XiaoHuYao-2.ttf'))

# 设置段落格式
styles = getSampleStyleSheet()  
# 加入支持中文的段落格式
styles.add(ParagraphStyle("text", fontName='lively', fontSize=18,
              leading=30))

# 接受flowables的列表
Elements = []

# 文档对象,showBoundary=1显示边界
doc = BaseDocTemplate('basedoc.pdf', showBoundary=1)

# 单栏页面页脚
def foot1(canvas, document):
    canvas.saveState()
    canvas.setFont('apple', 12)
    canvas.drawString(inch, 0.5 * inch, "单栏第{}页".format(doc.page))
    canvas.restoreState()
# 双栏页面页脚
def foot2(canvas, document):
    canvas.saveState()
    canvas.setFont('apple', 12)
    canvas.drawString(inch, 0.5 * inch, "双栏第{}页".format(doc.page))
    canvas.restoreState()

# 单栏页面要用的单栏对象
frameT = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal')

# 双栏页面要用的双栏对象
frame1 = Frame(doc.leftMargin, doc.bottomMargin, doc.width / 2 - 6, doc.height, id='col1')
frame2 = Frame(doc.leftMargin + doc.width / 2 + 6, doc.bottomMargin, doc.width / 2 - 6,
         doc.height, id='col2')

# 将两个页面版式加入到文档对象里,文档对象的方法函数addPageTemplates()的参数是个列表
# 列表里放页面对象,页面对象由类PageTemplate生成
# 生成对象页面对象时frames=接收前面生成的frame对象,onPage=介绍前面定义的的函数名foot1和foot2
# foot1()和foot2()里放不用flowable实现直接往pdfgen.canvas画布上画的部分,比如页脚
doc.addPageTemplates([PageTemplate(id='OneCol', frames=frameT, onPage=foot1),
            PageTemplate(id='TwoCol', frames=[frame1, frame2], onPage=foot2),
            ])

# 框架搭好后往里面灌flowables,flowables先放进列表Elements
# 单栏页面的frame的id定义成'normal',不特别声明会使用normal所在的单栏页面版式
# 灌进一个段落,段落格式用的是前面定义的'text'
Elements.append(Paragraph("西风吹老洞庭波,一夜湘君白发多。醉后不知天在水,满船清梦压星河。" * 30, styles['text']))

# NextPageTemplate()用来切换页面版式,要切换到双栏页面
Elements.append(NextPageTemplate('TwoCol'))
# 必须马上跟上一个分页符,后面的内容才会立刻灌进新版式的页面里
Elements.append(PageBreak())
Elements.append(Paragraph("无可奈何花落去,似曾相识燕归来。小园香径独徘徊。" * 30, styles['text']))

# 切换到单栏页面
Elements.append(NextPageTemplate('OneCol'))
Elements.append(PageBreak())
Elements.append(Paragraph('''沧海笑 滔滔两岸潮 浮沉随浪记今朝
苍天笑 纷纷世上潮 谁负谁胜出天知晓
江山笑 烟雨遥 涛浪淘尽 红尘俗世知多少
清风笑 竟若寂寥 豪情还剩了一襟晚照
苍生笑 不再寂寥 豪情仍在痴痴笑笑''', styles['text']))

# 把Elements里的内容生成pdf文件
doc.build(Elements)