Reportlab生成PDF文件

word文档内的一页:

将文本取出来,生成自定义格式的PDF文件:

从word取出文本时标题的标号和页码是取不出来的,要自己加。另外就是目录也要自己生成和添加:

代码和解释如下:

from reportlab.lib.styles import ParagraphStyle as PS   
# 段落格式
from reportlab.platypus import PageBreak                
# 分页符
from reportlab.platypus.paragraph import Paragraph      
# 生成段落用
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate    # 用于生成页面模版,文档模版
from reportlab.platypus.tableofcontents import TableOfContents      # 用于生成目录
from reportlab.platypus.frames import Frame             
# 生成页面模版是定义页面内的框架frame
from reportlab.lib.units import cm, inch                
# 尺度单位

# 注册中文字体
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'))

# 定义标题、普通段落和代码的段落格式
h1 = PS(name = 'Heading1', fontSize = 16, leading = 24, fontName = 'lively', spaceAfter = 15)
h2 = PS(name = 'Heading2', fontSize = 14, leading = 20, leftIndent = 10, fontName = 'lively', spaceBefore = 10, spaceAfter = 10)
h3 = PS(name = 'Heading3', fontSize = 12, leading = 16, leftIndent = 20, fontName = 'lively', spaceBefore = 10, spaceAfter = 10)
normalText = PS(name = 'normal', fontSize = 12, leading = 14, firstLineIndent = 25, fontName = 'apple')
code = PS(name = 'code', fontSize = 10, leading = 14, leftIndent = 35,fontName = 'apple')


class MyDocTemplate(BaseDocTemplate):   
# 是BaseDocTemplate的子类

    def __init__(self, filename, **kw):
        BaseDocTemplate.__init__(self, filename, **kw)
        template = PageTemplate('normal', [Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1')], onPageEnd=self.footer)
        # 定义页面模版,页脚可有可无
        self.addPageTemplates(template)     # 加入页面模版

    # 打开word取出文字内容,生成段落flowable放进story
    def feedContent(self, filename, story):
        from docx import Document           # 帮忙处理word文档
        docWord = Document(filename)        
        # 打开word文档,生成word文档对象

        h1Num = 0; h2Num = 0; h3Num = 0     
        # 因为要给标题加序号,三级序号,所以用变量记录所在层级的序号
        for para in docWord.paragraphs:     
        # 遍历word文档的各个段落
            docStyle = para.style.name      
            # 取出word文档中的段落格式
            if docStyle == 'Heading 1':     
            # 如果是第一级标题,先加上序号再填进story
                h1Num += 1                  
                # 标记第一级标题的序号增1
                seq = str(h1Num) + '、 '    
                # 第一级标题的格式为1、
                story.append(Paragraph(seq + para.text, h1))    # 生成段落followable加入story
            elif docStyle == 'Heading 2':   # 第二级标题
                h2Num += 1                  
                # 标记第二级标题的序号增1
                seq = str(h1Num) + '.' + str(h2Num) + ' '   
                # 第二级标题格式1.1
                story.append(Paragraph(seq + para.text, h2))
            elif docStyle == 'Heading 3':   # 第三级标题
                h3Num += 1                  
                # 标记第三级标题的序号增1
                seq = str(h1Num) + '.' + str(h2Num) + '.' + str(h3Num) + ' '
                # 第三级标题的序号1.1.1
                story.append(Paragraph(seq + para.text, h3))
            elif docStyle == 'Code':        
            # 普通段落和代码直接填进story,注意代码用Preformatted()保留代码的缩进格式(杭首空格)
                from reportlab.platypus import Preformatted
                story.append(Preformatted(para.text, code))
            else:
                story.append(Paragraph(para.text, normalText))

    # 生成一个flowable后判断是否为标题,是标题填进目录,二三级目录生成书签链接
    def afterFlowable(self, flowable):  # 注册目录的条目
        if flowable.__class__.__name__ == 'Paragraph':
            text = flowable.getPlainText()      # 取出文字
            style = flowable.style.name         # 取出段落格式
            if style == 'Heading1':             # 第一级标题
                self.notify('TOCEntry', (0, text, self.page))
            if style == 'Heading2':             # 第二级标题
                key = 'h2-{}'.format(self.seq.nextf('heading2'))    # 生成书签名
                self.canv.bookmarkPage(key)     # 生成书签页
                self.notify('TOCEntry', (1, text, self.page, key))
                # 'TOCEntry':通知类型;(1, text, self.page, key):
                # (目录层级、文本内容、当前页数、可选的书签名)
            if style == 'Heading3':             # 第三级标题
                key = 'h3-{}'.format(self.seq.nextf('heading3'))
                self.canv.bookmarkPage(key)
                self.notify('TOCEntry', (2, text, self.page, key))

    # 页脚给出第几页
    def footer(self, myCanvas, myDoc):
        myCanvas.setFont('lively',10)           # 设置字体
        myCanvas.drawRightString(7.6*inch,.5*inch, "{}".format(self.page))  
        # 画页脚


toc = TableOfContents()                         # 生成目录对象
toc.levelStyles = [h1, h2, h3]                  
# 定义目录的格式,三层,每层的格式用段落格式定义

story = []                                      
# 列表story存放生成的各个flowable
story.append(toc)                               
# 将目录对象填进story,此时目录只是架子,内容还没填进去
story.append(PageBreak())                       # 分页符

docPDF = MyDocTemplate('mintoc.pdf')            
# 生成PDF文档对象
docPDF.feedContent('excerpt.docx', story)       
# 打开word文档,取出内容生成flowable放进story中
docPDF.multiBuild(story)
# 这个文档的生成要过两遍,第一遍加了个目录的空架子,
# 文档内容生成之后才能把目录内容填进去,所以用multiBuild()而不是build()