Differences between revisions 1 and 2
Revision 1 as of 2008-08-10 00:41:31
Size: 44096
Editor: ZoomQuiet
Comment:
Revision 2 as of 2008-08-10 00:41:33
Size: 44096
Editor: ZoomQuiet
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1175: Line 1175:
创建 by -- ZoomQuiet [[[DateTime(2008-08-10T00:41:31Z)]]] 创建 by -- ZoomQuiet [[[DateTime(2008-08-10T00:41:33Z)]]]

TableOfContents

Include(ZPyUGnav)

用 python-reportlab 将 rst 转换为中文 PDF

Jiahua Huang <[email protected]>
reply-to        [email protected]
to      [email protected]
date    Sat, Aug 9, 2008 at 01:22

subject [CPyUG:61848] [:MiscItems/2008-08-08:Re: 简单使用 Reportlab 生成中文 PDF 文档]

{{{#rst


用 python-reportlab 将 rst 转换为中文 PDF


:Author: Jiahua Huang <[email protected]> :Keywords: RST, PDF :License: LGPL :Requires: docutils, reportlab

python 里用 Reportlab 应该比用 tex、docbook、fop 合适, 不过 python-reportlab 默认没中文字体,也没中文换行,

分析


实际上新的 python-reportlab 已经有能力处理中文字体,也有了一个基本的 **CJK Warp** (breakLinesCJK),只是通常使用 python-reportlab 的程序都没有去注意。

现有 reportlab 程序主要问题就是::

  1. 使用默认英文字体,而没考虑中文字
  2. 使用默认英文折行,而没考虑中文长行

解决 ====

那么,针对现有问题,可以考虑如下方向解决:

+


+


+ | **问题** | **解决** | +====================+========================================+ | 没有中文字体 | 注册中文字体文泉驿正黑 | +


+


+ | 默认不是中文字体 | 覆盖选字处理,一律用文泉驿正黑 | +


+


+ | 默认不是 CJK 换行 | 覆盖折行处理,一律尝试 breakLinesCJK | +


+


+

动手吧


拿把斧头直接砍 python-reportlab 当然省事, 不过系统的库修改了以后维护还真是麻烦,能不能不修改库本身呢?

既然 Python 是动态语言,可以运行中动态修改几乎一切东西, 那么运行时修改 reportlab 的类吧,只要在最终调用 reportlab 生成 PDF 前动态修改到自己要的就行

注册字体 ========

因为主要是自己用,就硬编码使用自己喜欢的文泉驿正黑字体吧,

用 reportlab.pdfbase.pdfmetrics.registerFont 注册文泉驿::

  • import reportlab.rl_config reportlab.rl_config.warnOnMissingFontGlyphs = 0 import reportlab.pdfbase.pdfmetrics import reportlab.pdfbase.ttfonts reportlab.pdfbase.pdfmetrics.registerFont(reportlab.pdfbase.ttfonts.TTFont('song',
    • '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttf'))

改默认字体 ==========

先好歹让中文能显示吧,不如暂时让所有字体都用 文泉驿 (刚才把文泉驿注册为"song", 所以这里用 "song" 字体)

修改 reportlab.lib.fonts.ps2tt 和 reportlab.lib.fonts.tt2ps::

  • import reportlab.lib.fonts reportlab.lib.fonts.ps2tt = lambda psfn: ('song', 0, 0) reportlab.lib.fonts.tt2ps = lambda fn,b,i: 'song'

中文换行 ========

为了省事,就不考虑哪些要 CJK 折行哪些不要了,直接 try: CJK 换行, except 再英文换行

修改 reportlab.platypus.Paragraph.wrap::

  • import reportlab.platypus def wrap(self, availWidth, availHeight):
    • # work out widths array for breaking self.width = availWidth leftIndent = self.style.leftIndent first_line_width = availWidth - (leftIndent+self.style.firstLineIndent) - self.style.rightIndent later_widths = availWidth - leftIndent - self.style.rightIndent try:
      • self.blPara = self.breakLinesCJK([first_line_width, later_widths])
      except:
      • self.blPara = self.breakLines([first_line_width, later_widths])
      self.height = len(self.blPara.lines) * self.style.leading return (self.width, self.height)
    reportlab.platypus.Paragraph.wrap = wrap

完整的一段 ==========

就是将上边 3 个合起来::

  • import reportlab.rl_config reportlab.rl_config.warnOnMissingFontGlyphs = 0 import reportlab.pdfbase.pdfmetrics import reportlab.pdfbase.ttfonts reportlab.pdfbase.pdfmetrics.registerFont(reportlab.pdfbase.ttfonts.TTFont('song', '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttf')) import reportlab.lib.fonts reportlab.lib.fonts.ps2tt = lambda psfn: ('song', 0, 0) reportlab.lib.fonts.tt2ps = lambda fn,b,i: 'song' ## for CJK Wrap import reportlab.platypus def wrap(self, availWidth, availHeight):
    • # work out widths array for breaking self.width = availWidth leftIndent = self.style.leftIndent first_line_width = availWidth - (leftIndent+self.style.firstLineIndent) - self.style.rightIndent later_widths = availWidth - leftIndent - self.style.rightIndent try:
      • self.blPara = self.breakLinesCJK([first_line_width, later_widths])
      except:
      • self.blPara = self.breakLines([first_line_width, later_widths])
      self.height = len(self.blPara.lines) * self.style.leading return (self.width, self.height)
    reportlab.platypus.Paragraph.wrap = wrap

使用


具体的说了那么多,那实际怎么做呢?

前面说了, Python 是动态语言, 你可以将上边的代码随便插入到你的任何程序或模块里,只要在实际调用 reportlab 生成 PDF 前就可以了

1. 直接点,在你的程序 import reportlab 前插入上边的代码 2. 如果想让你原先的文件不要插入这突兀的语句,那可以将上边的代码写入到如 cjkreportlab.py, 在你需要用 reportlab 做中文 pdf 的代码里加个 import cjkreportlab

例子


这里给个修改的 rst2pdf.py::

  • #!/usr/bin/env python # -*- coding: utf-8 -*- import docutils.core,docutils.nodes,sys,re import sys import pprint

    from types import StringType from docutils import version, version_details, SettingsSpec from docutils import frontend, io, utils, readers, writers from docutils.frontend import OptionParser from docutils.transforms import Transformer import docutils.readers.doctree from urlparse import * from reportlab.platypus import *

    #from reportlab.platypus.para import Paragraph,FastPara,Para from reportlab.pdfbase.pdfmetrics import stringWidth from reportlab.lib.enums import * import reportlab.lib.colors as colors from reportlab.lib.units import * from reportlab.lib.pagesizes import * from copy import copy from cgi import escape try:

    • from wordaxe.rl.paragraph import Paragraph

      from wordaxe.rl.styles import ParagraphStyle, getSampleStyleSheet

    except:
    • print "No support for hyphenation, install wordaxe"
    ## 就是在这里添加前面说的代码啦 ############################ import reportlab.rl_config reportlab.rl_config.warnOnMissingFontGlyphs = 0 import reportlab.pdfbase.pdfmetrics import reportlab.pdfbase.ttfonts reportlab.pdfbase.pdfmetrics.registerFont(reportlab.pdfbase.ttfonts.TTFont('song', '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttf')) import reportlab.lib.fonts reportlab.lib.fonts.ps2tt = lambda psfn: ('song', 0, 0) reportlab.lib.fonts.tt2ps = lambda fn,b,i: 'song' ## for CJK Wrap import reportlab.platypus def wrap(self, availWidth, availHeight):
    • # work out widths array for breaking self.width = availWidth leftIndent = self.style.leftIndent first_line_width = availWidth - (leftIndent+self.style.firstLineIndent) - self.style.rightIndent later_widths = availWidth - leftIndent - self.style.rightIndent try:
      • self.blPara = self.breakLinesCJK([first_line_width, later_widths])
      except:
      • self.blPara = self.breakLines([first_line_width, later_widths])
      self.height = len(self.blPara.lines) * self.style.leading return (self.width, self.height)
    reportlab.platypus.Paragraph.wrap = wrap import sys reload(sys) sys.setdefaultencoding('utf8') ######################################################## ## style ########################################################################################## # Demo stylesheet module.for rst2pdf from reportlab.platypus import * import reportlab.lib.colors as colors from reportlab.lib.units import * from reportlab.pdfbase.ttfonts import TTFont from reportlab.lib.fonts import addMapping from reportlab.lib.styles import * from reportlab.lib.enums import * from reportlab.pdfbase import pdfmetrics try:
    • from wordaxe.rl.paragraph import Paragraph

      from wordaxe.rl.styles import ParagraphStyle,getSampleStyleSheet

    except:
    • print "No hyphenation support install wordaxe"
    import os # Set these, and then **maybe** think of setting the stylesheets below # if you want finer control stdFont = 'song' stdBold = 'song' stdItalic = 'song' stdBoldItalic = 'song' stdMono = 'song' # You can embed your own fonts and use them later if you want if os.path.isfile('wrabbit/WHITRABT.TTF'): def getStyleSheet():
    • """Returns a stylesheet object"""

      stylesheet = StyleSheet1()

      stylesheet.add(ParagraphStyle(name='Normal',

      • fontName=stdFont, bulletFontName=stdFont, fontSize=10, bulletFontSize=10, leading=12, language='EN', hyphenation=True
      • ))

      stylesheet.add(ParagraphStyle(name='BodyText',

      • parent=stylesheet['Normal'], spaceBefore=6, language='EN', hyphenation=True
      • ))

      stylesheet.add(ParagraphStyle(name='Footer',

      • parent=stylesheet['Normal'], backColor='#EFEFEF', alignment=TA_CENTER)
      • )

      stylesheet.add(ParagraphStyle(name='Attribution',

      • parent=stylesheet['BodyText'], alignment=TA_RIGHT)

      • )

      stylesheet.add(ParagraphStyle(name='FieldName',

      • parent=stylesheet['BodyText'], alignment=TA_RIGHT, fontName=stdBold, bulletFontName=stdBold, )

      • )

      stylesheet.add(ParagraphStyle(name='Rubric',

      • parent=stylesheet['BodyText'], textColor=colors.darkred, alignment=TA_CENTER)

      • )

      stylesheet.add(ParagraphStyle(name='Italic',

      • parent=stylesheet['BodyText'], fontName = stdItalic, bulletFontName=stdItalic)

      • )

      stylesheet.add(ParagraphStyle(name='Title',

      • parent=stylesheet['Normal'], fontName = stdBold, bulletFontName=stdBold, fontSize=18, bulletFontSize=18, leading=22, alignment=TA_CENTER, spaceAfter=6),
      • alias='title')

      stylesheet.add(ParagraphStyle(name='Subtitle',

      • parent=stylesheet['Title'], fontSize=14, bulletFontSize=14),
      • alias='subtitle')

      stylesheet.add(ParagraphStyle(name='Heading1',

      • parent=stylesheet['Normal'], fontName = stdBold, bulletFontName=stdBold, fontSize=18, bulletFontSize=18, leading=22, keepWithNext=True, spaceAfter=6),
      • alias='h1')

      stylesheet.add(ParagraphStyle(name='Heading2',

      • parent=stylesheet['Normal'], fontName = stdBold, bulletFontName=stdBold, fontSize=14, bulletFontSize=14, leading=18, spaceBefore=12, keepWithNext=True, spaceAfter=6),
      • alias='h2')

      stylesheet.add(ParagraphStyle(name='Heading3',

      • parent=stylesheet['Normal'], fontName = stdBoldItalic, bulletFontName=stdBoldItalic, fontSize=12, bulletFontSize=12, leading=14, spaceBefore=12, keepWithNext=True, spaceAfter=6),
      • alias='h3')

      stylesheet.add(ParagraphStyle(name='Heading4',

      • parent=stylesheet['Normal'], fontName = stdBoldItalic, bulletFontName=stdBoldItalic, fontSize=12, bulletFontSize=12, leading=14, spaceBefore=12, keepWithNext=True, spaceAfter=6),
      • alias='h4')

      stylesheet.add(ParagraphStyle(name='Bullet',

      • parent=stylesheet['Normal'], firstLineIndent=0, spaceBefore=3),
      • alias='bu')

      stylesheet.add(ParagraphStyle(name='Definition',

      • parent=stylesheet['Normal'], firstLineIndent=0, leftIndent=36, bulletIndent=0, spaceBefore=6, fontName=stdBold, bulletFontName=stdBold),
      • alias='df')

      stylesheet.add(ParagraphStyle(name='Code',

      • parent=stylesheet['Normal'], fontName=stdMono, bulletFontName=stdMono, fontSize=8, bulletFontSize=8, leading=8.8, firstLineIndent=0, leftIndent=12, spaceBefore=4))
      return stylesheet
    # Some table styles used for pieces of the document tstyles={} # Used for regular tables tstyleNorm = [ ('VALIGN',(0,0),(-1,-1),'TOP'),
    • ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black),
    • ]
    # Header row in tables tstyleHead = ('BACKGROUND',(0,0),(-1,0),colors.yellow)

    tstyles['Normal']=TableStyle(tstyleNorm) # Used for field lists

    tstyles['Field']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),

    • ('ALIGNMENT',(0,0),(1,-1),'RIGHT'),
    • ])
    fieldlist_lwidth=3*cm # Used for endnotes

    tstyles['Endnote']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),

    • ('ALIGNMENT',(0,0),(1,-1),'RIGHT'),
    • ])
    # Left column of the endnote. The content of the note takes the rest of # the available space endnote_lwidth=2*cm # Used for sidebars

    tstyles['Sidebar']=TableStyle([ ('VALIGN',(0,0),(-1,-1),'TOP'),

    • ('BACKGROUND',(0,0),(-1,-1),colors.lightyellow),
    • ])
    ################################################################################################### styles=getStyleSheet() try: except:
    • print "No support for hyphenation, install wordaxe"
    # This is A4 paper, change it as you wish pw,ph=ps=A4 # Margins tm=2*cm bm=2*cm lm=3*cm rm=1.5*cm # tw is the text width. # We need it to calculate header-footer height # and compress literal blocks. tw=pw-lm-rm page_break_level=0

    marks="#=-_*^>%&|" lowerroman=['i','ii','iii','iv','v','vi','vii','viii','ix','x','xi'] loweralpha="abcdefghijklmnopqrstuvwxyz"

    class MyIndenter(Indenter):

    • # Bugs in reportlab? def draw(self):
      • pass
      width=0 height=0
    def depth (node):
    • if node.parent==None:
      • return 0
      else:
      • return 1+depth(node.parent)
    decoration = {'header':None, 'footer':None, 'endnotes':[]} def gather_pdftext (node, depth, in_line_block=False,replaceEnt=True):
    • return .join([gen_pdftext(n,depth,in_line_block,replaceEnt) for n in node.children ])

    def gen_pdftext(node, depth, in_line_block=False,replaceEnt=True):
    • pre="" post="" if isinstance (node, docutils.nodes.paragraph) \
      • or isinstance (node, docutils.nodes.title) \ or isinstance (node, docutils.nodes.subtitle) \ :
      • node.pdftext=gather_pdftext(node,depth)+"\n"
      elif isinstance (node, docutils.nodes.Text):
      • node.pdftext=node.astext() if replaceEnt:
        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.strong):
      • pre="<b>" post="</b>" node.pdftext=gather_pdftext(node,depth) if replaceEnt:

        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.emphasis):
      • pre="<i>" post="</i>" node.pdftext=gather_pdftext(node,depth) if replaceEnt:

        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.literal):
      • pre='<font face="%s">'%styles['Code'].fontName post="</font>" node.pdftext=gather_pdftext(node,depth) if replaceEnt:

        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.superscript):
      • pre='<super>' post="</super>" node.pdftext=gather_pdftext(node,depth) if replaceEnt:

        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.subscript):
      • pre='<sub>' post="</sub>" node.pdftext=gather_pdftext(node,depth) if replaceEnt:

        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.title_reference):
      • # FIXME needs to work as a link

        pre='<font color="blue">' post="</font>" node.pdftext=gather_pdftext(node,depth) if replaceEnt:

        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.reference) :
      • pre='<font color="blue">' post="</font>" uri=node.get('refuri') if uri:

        • if urlparse(uri)[0]:
          • pre+=u'<a href="%s">'%uri post='</a>'+post

        else:
        • uri=node.get('refid') if uri:
          • pre+=u'<a href="#%s">'%uri post='</a>'+post

        node.pdftext=node.astext() if replaceEnt:
        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.option_string) \
      • or isinstance (node, docutils.nodes.option_argument) \ :
      • node.pdftext=node.astext() if replaceEnt:
        • node.pdftext=escape(node.pdftext,True)
      elif isinstance (node, docutils.nodes.header) \
      • or isinstance (node, docutils.nodes.footer) \
        • :
      • node.pdftext=gather_pdftext(node,depth) if replaceEnt:
        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.system_message) \
      • or isinstance (node, docutils.nodes.problematic) \ :
      • sys.stderr.write (node.astext()+"\n") sys.stderr.flush()

        pre='<font color="red">' post="</font>" node.pdftext=gather_pdftext(node,depth) if replaceEnt:

        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.generated):
      • node.pdftext=gather_pdftext(node,depth) if replaceEnt:
        • node.pdftext=escape(node.pdftext,True)
        node.pdftext=pre+node.pdftext+post
      elif isinstance (node, docutils.nodes.image):
      • node.pdftext='<img src="%s" />'%node.get('uri')

      elif isinstance (node, docutils.nodes.footnote_reference):
      • # Fixme link to the right place

        node.pdftext=u'<super><font color="blue">%s</font></super>'%node.astext()

      elif isinstance (node, docutils.nodes.citation_reference):
      • # Fixme link to the right place

        node.pdftext=u'<font color="blue">[%s]</font>'%node.astext()

      # FIXME nodes we are ignoring for the moment elif isinstance (node, docutils.nodes.target):
      • # FIXME: make it work as a target for links node.pdftext=gather_pdftext(node,depth) if replaceEnt:
        • node.pdftext=escape(node.pdftext,True)
      else:
      • print "Unkn. node (gen_pdftext): ", node.class print node #print node.transform sys.exit(1)

      return node.pdftext

    def PreformattedFit(text,style):

    • """Preformatted section that gets horizontally compressed if needed.""" w=max(map(lambda line:stringWidth(line,style.fontName,style.fontSize),text.splitlines())) mw=tw-style.leftIndent-style.rightIndent

      if w>mw:

      • style=copy(style) f=max((0.375,mw/w)) style.fontSize*=f style.leading*=f
      return Preformatted(text,style)

    def gen_elements(node, depth, in_line_block=False, style=styles['BodyText']):

    • global decoration if isinstance (node, docutils.nodes.document):
      • node.elements=gather_elements(node,depth,style=style)
      ####################### ## Tables ####################### elif isinstance (node, docutils.nodes.table):
      • node.elements=gather_elements(node,depth)
      elif isinstance (node, docutils.nodes.tgroup):
      • rows=[] hasHead=False for n in node.children:
        • if isinstance (n,docutils.nodes.thead):
          • hasHead=True for row in n.children:
            • r=[] for cell in row.children:
              • r.append(cell)
              rows.append(r)
          elif isinstance (n,docutils.nodes.tbody):
          • for row in n.children:
            • r=[] for cell in row.children:
              • r.append(cell)
              rows.append(r)
        spans=filltable (rows) data=[] for row in rows:
        • r=[] for cell in row:
          • if isinstance(cell,str):
            • r.append("")
            else:
            • r.append(gather_elements(cell,depth))
          data.append(r)
        st=spans+tstyleNorm if hasHead:
        • st+=[tstyleHead]

        node.elements=[Table(data,style=TableStyle(st))]

      elif isinstance (node, docutils.nodes.title):
      • # Special cases: (Not sure this is right ;-) if isinstance (node.parent, docutils.nodes.document):

        • # FIXME maybe make it a coverpage? node.elements=[Paragraph(gen_pdftext(node,depth), styles['Title'])]
        elif isinstance (node.parent, docutils.nodes.topic):
        • # FIXME style correctly node.elements=[Paragraph(gen_pdftext(node,depth), styles['Heading3'])]
        elif isinstance (node.parent, docutils.nodes.admonition) or \
        • isinstance (node.parent, docutils.nodes.sidebar):
        • node.elements=[Paragraph(gen_pdftext(node,depth), styles['Heading3'])]
        else:
        • node.elements=[Paragraph(gen_pdftext(node,depth), styles['Heading%d'%min(depth,3)])]
      elif isinstance (node, docutils.nodes.subtitle):
      • if isinstance (node.parent,docutils.nodes.sidebar):
        • node.elements=[Paragraph(gen_pdftext(node,depth), styles['Heading4'])]
        elif isinstance (node.parent,docutils.nodes.document):
        • node.elements=[Paragraph(gen_pdftext(node,depth), styles['Subtitle'])]
      elif isinstance (node, docutils.nodes.paragraph):
      • node.elements=[Paragraph(gen_pdftext(node,depth), style)]
      elif isinstance (node, docutils.nodes.docinfo):
      • # A docinfo usually contains several fields. # We'll render it as a series of elements, one field each. node.elements=gather_elements(node,depth,style=style)
      elif isinstance (node, docutils.nodes.field):
      • # A field has two child elements, a field_name and a field_body. # We render as a two-column table, left-column is right-aligned, # bold, and much smaller

        fn=Paragraph(gather_pdftext(node.children[0],depth)+":",style=styles['FieldName']) fb=gen_elements(node.children[1],depth) node.elements=[Table(fn,fb,style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])]

      elif isinstance (node, docutils.nodes.decoration):
      • # This is a tricky one. We need to switch our document's # page templates based on this. If decoration contains a # header and/or a footer, we need to use those # right now, we avoid trouble. # FIXME Implement node.elements=gather_elements(node,depth,style=style)
      elif isinstance (node, docutils.nodes.header):
      • decoration['header']=Paragraph(gather_pdftext(node,depth),style=styles['Footer']) node.elements=[]
      elif isinstance (node, docutils.nodes.footer):
      • decoration['footer']=Paragraph(gather_pdftext(node,depth),style=styles['Footer']) node.elements=[]
      elif isinstance (node, docutils.nodes.author):
      • if isinstance (node.parent,docutils.nodes.authors):
        • # Is only one of multiple authors. Return a paragraph node.elements=[Paragraph(gather_pdftext(node,depth), style=style)]
        else:
        • # A single author: works like a field fb=gather_pdftext(node,depth)

          node.elements=[Table([[Paragraph("Author:",style=styles['FieldName']),

          • Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])]
      elif isinstance (node, docutils.nodes.authors): elif isinstance (node, docutils.nodes.organization):
      • fb=gather_pdftext(node,depth)

        t=Table([[Paragraph("Organization:",style=styles['FieldName']),

        • Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])
        node.elements=[t]
      elif isinstance (node, docutils.nodes.contact):
      • fb=gather_pdftext(node,depth)

        t=Table([[ Paragraph("Contact:",style=styles['FieldName']),

        • Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])
        node.elements=[t]
      elif isinstance (node, docutils.nodes.address):
      • fb=gather_pdftext(node,depth)

        t=Table([[ Paragraph("Address:",style=styles['FieldName']),

        • Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])
        node.elements=[t]
      elif isinstance (node, docutils.nodes.version):
      • fb=gather_pdftext(node,depth)

        t=Table([[ Paragraph("Version:",style=styles['FieldName']),

        • Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])
        node.elements=[t]
      elif isinstance (node, docutils.nodes.revision):
      • fb=gather_pdftext(node,depth)

        t=Table([[ Paragraph("Revision:",style=styles['FieldName']),

        • Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])
        node.elements=[t]
      elif isinstance (node, docutils.nodes.status):
      • fb=gather_pdftext(node,depth)

        t=Table([[ Paragraph("Version:",style=styles['FieldName']),

        • Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])
        node.elements=[t]
      elif isinstance (node, docutils.nodes.date):
      • fb=gather_pdftext(node,depth)

        t=Table([[ Paragraph("Date:",style=styles['FieldName']),

        • Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])
        node.elements=[t]
      elif isinstance (node, docutils.nodes.copyright):
      • fb=gather_pdftext(node,depth)

        t=Table([[Paragraph("Copyright:",style=styles['FieldName']),

        • Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])
        node.elements=[t]
      elif isinstance (node, docutils.nodes.topic) \
      • or isinstance (node, docutils.nodes.field_body) \ :
      • node.elements=gather_elements(node,depth,style=style)
      elif isinstance (node, docutils.nodes.section):
      • if depth<page_break_level:

        • node.elements=[PageBreak()]+gather_elements(node,depth+1)

        else:
        • node.elements=gather_elements(node,depth+1)
      elif isinstance (node, docutils.nodes.bullet_list) \
      • or isinstance (node, docutils.nodes.enumerated_list) \ or isinstance (node, docutils.nodes.definition_list) \ or isinstance (node, docutils.nodes.option_list) \ or isinstance (node, docutils.nodes.field_list) \ or isinstance (node, docutils.nodes.definition) \ :
      • node.elements=gather_elements(node,depth,style=style)
      elif isinstance (node, docutils.nodes.option_list_item):
      • og = gather_elements(node.children[0],depth,style) desc = gather_elements(node.children[1],depth,style)

        node.elements=[Table(og,desc,style=tstyles['Field'])]

      elif isinstance (node, docutils.nodes.definition_list_item):
      • # I need to catch the classifiers here tt=[] dt=[] for n in node.children:
        • if isinstance(n,docutils.nodes.term) or \
          • isinstance(n,docutils.nodes.classifier) :
          • tt.append(gather_pdftext(n,depth,style))
          else:
          • dt=dt+gen_elements(n,depth,style)

        node.elements=[Paragraph(':'.join(tt),style),MyIndenter(left=10)]+dt+[MyIndenter(left=-10)]

      elif isinstance (node, docutils.nodes.list_item):
      • # A list_item is a table of two columns. # The left one is the bullet itself, the right is the # item content. This way we can nest them. el=gather_elements(node,depth,style=style) b="" if node.parent.get('bullet') or isinstance(node.parent,docutils.nodes.bullet_list):
        • # FIXME: use correct bullet symbols, check inter-paragraph spacing b=str(node.parent.get('bullet')) if b=="None":
          • b=""
        elif node.parent.get ('enumtype')=='arabic':
        • b=str(node.parent.children.index(node)+1)+'.'
        elif node.parent.get ('enumtype')=='lowerroman':
        • b=str(lowerroman[node.parent.children.index(node)])+'.'
        elif node.parent.get ('enumtype')=='upperroman':
        • b=str(lowerroman[node.parent.children.index(node)].upper())+'.'
        elif node.parent.get ('enumtype')=='loweralpha':
        • b=str(loweralpha[node.parent.children.index(node)])+'.'
        elif node.parent.get ('enumtype')=='upperalpha':
        • b=str(loweralpha[node.parent.children.index(node)].upper())+'.'
        else:
        • print "Unknown kind of list_item" print node.parent sys.exit(1)
        el[0].bulletText = b node.elements=el
      elif isinstance (node, docutils.nodes.transition):
      • node.elements=[Separation()]
      elif isinstance (node, docutils.nodes.system_message) \
      • or isinstance (node, docutils.nodes.problematic) \ :
      • # FIXME show the error in the document, red, whatever sys.stderr.write (node.astext()+"\n") sys.stderr.flush() node.elements=[]
      elif isinstance (node, docutils.nodes.block_quote): elif isinstance (node, docutils.nodes.attribution):
      • node.elements=[Paragraph(gather_pdftext(node,depth),styles['Attribution'])]
      elif isinstance (node, docutils.nodes.comment):
      • # Class that generates no output node.elements=[]
      elif isinstance (node, docutils.nodes.line_block):
      • # Obsolete? Let's do something anyway. # FIXME: indent or not? qstyle=copy(style) qstyle.leftIndent+=30 node.elements=gather_elements(node,depth,style=qstyle)
      elif isinstance (node, docutils.nodes.line):
      • # All elements in one line node.elements=[Paragraph(gather_pdftext(node,depth),style=style)]
      elif isinstance (node, docutils.nodes.literal_block) \
      • or isinstance (node, docutils.nodes.doctest_block) \ or isinstance (node, docutils.nodes.option) \
      • :

        node.elements=[PreformattedFit(gather_pdftext(node,depth,replaceEnt=False),styles['Code'])]

      elif isinstance (node, docutils.nodes.attention) \
      • or isinstance (node, docutils.nodes.caution) \ or isinstance (node, docutils.nodes.danger) \ or isinstance (node, docutils.nodes.error) \ or isinstance (node, docutils.nodes.hint) \ or isinstance (node, docutils.nodes.important) \ or isinstance (node, docutils.nodes.note) \ or isinstance (node, docutils.nodes.tip) \ or isinstance (node, docutils.nodes.warning) \ or isinstance (node, docutils.nodes.admonition) \
      :
      • node.elements=[Paragraph(node.tagname.title(),style=styles['Heading3'])]+gather_elements(node,depth,style=style)
      elif isinstance (node, docutils.nodes.image):
      • # FIXME handle all the other attributes i=Image(filename=str(node.get("uri"))) if node.get('align'):
        • i.hAlign=node.get('align').upper()
        node.elements=[i]
      elif isinstance (node, docutils.nodes.figure):
      • # The sub-elements are the figure and the caption, and't ugly if # they separate

        node.elements=[KeepTogether(gather_elements(node,depth,style=style))]

      elif isinstance (node, docutils.nodes.caption):
      • node.elements=[Paragraph('<i>'+gather_pdftext(node,depth)+'</i>',style=style)]

      elif isinstance (node, docutils.nodes.legend):
      • node.elements=gather_elements(node,depth,style=style)
      elif isinstance (node, docutils.nodes.sidebar): elif isinstance (node, docutils.nodes.rubric):
      • node.elements=[Paragraph(gather_pdftext(node,depth),styles['Rubric'])]
      elif isinstance (node, docutils.nodes.compound):
      • # FIXME think if this is even implementable node.elements=gather_elements(node,depth,style)
      elif isinstance (node, docutils.nodes.container):
      • # FIXME think if this is even implementable node.elements=gather_elements(node,depth,style)
      elif isinstance (node, docutils.nodes.substitution_definition):
      • node.elements=[]
      elif isinstance (node, docutils.nodes.tbody):
      • rows=[gen_elements(n,depth) for n in node.children] t=[] for r in rows:
        • if not r:
          • continue
          t.append(r)
        node.elements=[Table(t,style=tstyles['Normal'])]
      elif isinstance (node, docutils.nodes.footnote):
      • # It seems a footnote contains a label and a series of elements label=Paragraph(gather_pdftext(node.children[0],depth),style) contents=gather_elements(node,depth,style)[1:] decoration['endnotes'].append([label,contents]) node.elements=[]
      elif isinstance (node, docutils.nodes.label):
      • node.elements=[Paragraph(gather_pdftext(node,depth),style)]
      elif isinstance (node, docutils.nodes.Text):
      • node.elements=[Paragraph(gather_pdftext(node,depth),style)]
      elif isinstance (node, docutils.nodes.entry):
      • node.elements=gather_elements(node,depth,style)
      # FIXME nodes we are ignoring for the moment elif isinstance (node, docutils.nodes.target) \
      • or isinstance (node, docutils.nodes.footnote) \ or isinstance (node, docutils.nodes.citation) \ or isinstance (node, docutils.nodes.reference) \ or isinstance (node, docutils.nodes.raw) \
      • : node.elements=[]
      else:
      • print "Unkn. node (gen_elements): ", node.class print node sys.exit(1)

      # set anchors for internal references for id in node['ids']:
      • node.elements.insert(
        • node.elements and isinstance(node.elements[0], PageBreak) and 1 or 0, Paragraph('<a name="%s"/>'%id,style))

      return node.elements

    def gather_elements (node, depth, in_line_block=False,style=styles['BodyText']):

    • r=[] for n in node.children:
      • r=r+(gen_elements(n,depth,in_line_block,style=style))
      return r
    class Separation(Flowable):
    • " A simple <hr>-like thingie" def wrap(self,w,h):

      • self.w=w return (w,1*cm)
      def draw(self):
      • self.canv.line(0,0.5*cm,self.w,0.5*cm)

    class FancyPage(PageTemplate):

    • def init(self,_id,pw,ph,tm,bm,lm,rm,hh,fh,head,foot):

      • tw=pw-lm-rm #textframe=Frame(lm,tm+hh,tw,ph-tm-bm-hh-fh) textframe=Frame(lm,tm+hh,tw,ph-tm-bm-hh-fh,topPadding=hh,bottomPadding=fh) self.head=head self.hx=lm self.hy=ph-tm self.foot=foot self.fx=lm self.fy=bm

        PageTemplate.init(self,_id,[textframe])

      def beforeDrawPage(self,canv,doc):
      • if self.head:
        • self.head.drawOn(canv,self.hx,self.hy)
        if self.foot:
        • self.foot.drawOn(canv,self.fx,self.fy)
    def filltable (rows):
    • # If there is a multicol cell, we need to insert Continuation Cells # to make all rows the same length for y in range(0,len( rows)):
      • for x in range (0,len(rows[y])):
        • cell=rows[y][x] if isinstance (cell,str):
          • continue
          if cell.get("morecols"):
          • for i in range(0,cell.get("morecols")):
            • rows[y].insert(x+1,"")
      for y in range(0,len( rows)):
      • for x in range (0,len(rows[y])):
        • cell=rows[y][x] if isinstance (cell,str):
          • continue
          if cell.get("morerows"):
          • for i in range(0,cell.get("morerows")):
            • rows[y+i+1].insert(x,"")
      # Create spans list for reportlab's table style spans=[] for y in range(0,len( rows)):
      • for x in range (0,len(rows[y])):
        • cell=rows[y][x] if isinstance (cell,str):
          • continue
          if cell.get("morecols"):
          • mc=cell.get("morecols")
          else: mc=0 if cell.get("morerows"):
          • mr=cell.get("morerows")
          else: mr=0 if mc or mr:
          • spans.append(('SPAN',(x,y),(x+mc,y+mr)))
      return spans

    if name == "main":

    • input=open(sys.argv[1]).read() import docutils.core doc=docutils.core.publish_doctree(input) elements=gen_elements(doc,0) endnotes = decoration['endnotes'] if endnotes:
      • elements.append(Spacer(1,2*cm)) elements.append(Separation()) for n in decoration['endnotes']:
        • elements.append(Table(n[0],n[1],style=tstyles['Endnote'],colWidths=[endnote_lwidth,None]))

      head=decoration['header'] foot=decoration['footer'] if head:
      • hh=head.wrap(tw,ph)[1]
      else:
      • hh=0
      if foot:
      • fh=foot.wrap(tw,ph)[1]
      else:
      • fh=0

      # So, now, create the FancyPage with the right sizes and elements

      FP=FancyPage("fancypage",pw,ph,tm,bm,lm,rm,hh,fh,head,foot) # A basic document for us to write to 'rl_hello_platypus.pdf'

      pdfdoc = BaseDocTemplate(sys.argv[1]+'.pdf',pageTemplates=[FP],showBoundary=0,pagesize=ps) pdfdoc.build(elements)

唔,您可以拿本 RST 文件来测试 rst2pdf 中文,注意编码得是 utf8

命令::

  • /rst2pdf.py XXX.rst

其他问题


现存问题::

  1. 硬编码的 文泉驿正黑字体 路径不能灵活适应不同系统
  2. 文泉驿正黑中文没有粗体和斜体
  3. 由于没有选字,而是一律文泉驿字体,使得英文也没有了粗斜体

您说,本文做的 pdf 没书签没目录?

唔,这是这个 rst2pdf 程序原先的问题,本文只解决 python-reportlab 中文显示和换行。

还有其他问题么? 请洽 python-cn 的老大 Zoomq 大妈 (Zoom. Quiet <[email protected]>) ~ 奥运进行时,请尽情搔扰~ }}}


反馈

创建 by -- ZoomQuiet [DateTime(2008-08-10T00:41:33Z)]

PageComment2

[:/PageCommentData:PageCommentData]

UsageRstReportlabExPdf (last edited 2009-12-25 07:09:16 by localhost)