python - ReportLab 变量 nextPageTemplate 的

标签 python reportlab

我正在将 pfaf.org(面向 future 的植物)的数据库转换为 pdf 书籍。 我在页面模板方面遇到了一些障碍。

每个植物可以从左对齐或右对齐的页面开始;并且可能有两页或更多页。 我有两个用于第一个植物页面的模板(左和右),还有两个用于潜在后续页面的模板。

目前处理如下(例如):

for i, plant in enumerate(plants):
  #the first plant is printed on a right-hand page
  if i % 2 == 0:
    template = 'left'
    second = 'right'
  else:
    template = 'right'
    second = 'left'

  Story.append(nextPageTemplate(template,'*', 'secondary_'+template, 'secondary_'+second))
  Story.append(PageBreak())
  #append various paragraphs, jumping between frames, etc...

代码(正如您可能知道的那样)对于单页植物来说效果很好。 对于多页植物,它也按预期工作(半)。

但是,正如您可能也看到的那样,两页(或四页等)的植物将破坏模板排列,因为上面的代码根据植物编号而不是页码假定页面位置。

我在上面代码的位置(即在 Story.append 周期期间)看不到此问题的解决方案 - 因为此时我无法判断工厂是否使用了多个页面,因此无法判断我当前所在的页面就是这样。

我希望我可以从自定义 docTemplate 中调整 nextPageTemplate 结构,但我不知道这是否可行。

是吗?或者还有其他解决方案吗?非常感谢任何帮助。我已经通读了一遍,但我能找到的最好的例子并不能完全涵盖这种情况。

有什么问题请追问。 谢谢


谢谢你,尼茨尔:
问题是我不知道每株植物会占用多少页。 例如 - 一个新植物从一个奇怪的页面开始,所以我给它一个模板循环('right','*',' secondaryLeft',' secondaryRight')。 [辅助页面只是具有适当边距的单个框架。]

如果该植物只有一页长,没问题,下一个植物将具有与上面相反的模板周期。 然而,如果植物有两页,它将导致后面的植物再次落在奇数页上,因此模板周期不应该改变......我希望这是有道理的。

这是我无法解决的情况...如果我按照你说的做,它不允许多页面植物。我的大部分代码如下;我已经尝试将其精简一点:)希望它仍然包含所有相关的内容,并且没有太多不必要的内容。

import os
import sys
import MySQLdb

from reportlab.platypus import Spacer, Image, Table, TableStyle, PageBreak, FrameBreak, paraparser
from reportlab.platypus.doctemplate import BaseDocTemplate, PageTemplate, NextPageTemplate, _doNothing
from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.platypus.frames import Frame
from reportlab.platypus.flowables import KeepInFrame
from reportlab.platypus.paragraph import Paragraph

from reportlab.lib.units import mm, cm
from reportlab.lib.pagesizes import A4, A5
from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER, TA_RIGHT
from reportlab.lib.styles import StyleSheet1, ParagraphStyle as PS
from reportlab.lib import colors

from reportlab.graphics.shapes import Drawing, Rect, String

from reportlab.pdfbase.pdfmetrics import registerFont, stringWidth
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.rl_config import warnOnMissingFontGlyphs
warnOnMissingFontGlyphs = 0

registerFont(TTFont('Museo_', '/home/wuwei/.fonts/Museo300-Regular.ttf'))
registerFont(TTFont('Museo_M', '/home/wuwei/.fonts/Museo500-Regular.ttf'))
registerFont(TTFont('Trebuchet', '/usr/share/fonts/truetype/msttcorefonts/Trebuchet_MS.ttf'))
registerFont(TTFont('Trebuchet_I', '/usr/share/fonts/truetype/msttcorefonts/Trebuchet_MS_Italic.ttf'))

## SOME VARIABLE DEFINITIONS ##

titleFont = "Museo_M"
subtitleFont = "Museo_"
stdFont = "Trebuchet"
stdItalic = "Trebuchet_I"
#stdSize = 14

"""CREATE GLOBALS"""
_w, _h = A4
_head_w = 17.5*cm
_head_pad = 0.2*cm
_main_w = 17.2*cm
_budge = 0.3*cm

_left_margin = 1.5*cm
_right_margin = 2.0*cm
_top_margin = 1.5*cm
_bottom_margin = 2.0*cm

_latinFontS = 18

#reset superFraction to style 'common name' placement
paraparser.superFraction = 0.15
paraparser.sizeDelta = 0


###########################################################################################################
#########################################                   ###############################################
########################################     DB FUNCTIONS     #############################################
#########################################                   ###############################################
###########################################################################################################

def connectToDB():
    try:
        connection = MySQLdb.connect (host = "localhost", 
                                user = "root", 
                                passwd = "****************", 
                                db = "pfaf")
    except MySQLdb.Error, e:
        print "I guess, either you don't have a local copy of the pfaf db"
        print "or something is wrong with your connection details."
        print "Error %d: %s" % (e.args[0], e.args[1])
        sys.exit (1)

    return connection

def close(item, exit=0):
    #used to close both database cursors and connections
    item.close()
    if exit == 1:
        sys.exit (0)

def runQuery(q, conn):
    results = conn.cursor(MySQLdb.cursors.DictCursor)
    results.execute (q)
    return results

def Fetch(results, fetchAll=0):
    if fetchAll:
        print "fetchAll"
        # FETCHALL option:
        rows = results.fetchall()
        #cursor.close()
        #conn.close()
        '''for row in rows:
            print "%s, %s" % (row["Latin Name"], row["Hardyness"])
        print "%d rows were returned" % results.rowcount'''
        return rows
    else:
        # FETCHONE option: 
        ##--- Print some debug info to command line ---##
        print "Latin Name  -  Common Name  -  Hardyness"       
        while (1):
            row = results.fetchone()
            if row == None:
                break

            latin_name = row["Latin Name"]
            common_name = row["Common name"]
            hardyness = row["Hardyness"]
            family = row["Family"]
            synonyms = row["Synonyms"]

        ##--- Print some more useful debug info to command line ---##
            print "%s  -  %s  -  %s" % (latin_name, common_name, hardyness)
        print row

        if results.rowcount != 1:
            print "%d rows were returned" % results.rowcount
        else:
            print "%d row was returned" % results.rowcount

        return row


###########################################################################################################
#########################################                   ###############################################
########################################  STORY PROCESSING    #############################################
#########################################                   ###############################################
###########################################################################################################

def drawBorders(canv, side):
    canv.saveState()
    d = Drawing(0,0)

    #header border#
    r = Rect( side-_budge, _h-(2.4*cm), _head_w+(_budge*2), 1.2*cm, rx=5, ry=5 )
    r.strokeColor = colors.black
    r.fillColor = colors.white
    r.strokeWidth = 1.5
    d.add(r)

    #hardyness border#
    rad = 5
    hWidth = 1.4*cm
    if side == _left_margin:
        hPos = -rad
    else:
        hPos = _w - hWidth + rad
    r = Rect( hPos, _h-(3.8*cm), hWidth, 1.2*cm, rx=rad, ry=rad )
    r.strokeColor = colors.black
    r.fillColor = colors.white
    r.strokeWidth = 1.5
    d.add(r)

    d.drawOn(canv, 0, 0)
    canv.restoreState()

def drawFooter(canv, doc):
    canv.saveState()
    canv.setFont(stdFont,10)
    canv.drawCentredString((_w/2.0), 1.5*cm, "%d - %s" % (doc.page, doc.latinName))        
    canv.restoreState()


class LeftPageTemplate(PageTemplate):
    def __init__(self):
        #allow a bigger margin on the right for binding
        latinF =    Frame(_left_margin, 27.5*cm,  _head_w,  0.8*cm,     id='latinL',    showBoundary=0, 
                          rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
        hardyF =    Frame(0.1*cm, 26.05*cm,   cm,  cm,  id='hardL',       showBoundary=0, 
                          rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
        synF =      Frame(_left_margin, 26.65*cm,   _main_w,  0.55*cm,  id='synL',       showBoundary=0, 
                          rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
        otherF =    Frame(_left_margin, 22.1*cm,   12.4*cm,  4.5*cm,  id='otherL',       showBoundary=1)
        calF =      Frame(14.2*cm, 22.1*cm,   4.5*cm,  4.5*cm,  id='calL',       showBoundary=0, 
                          rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
        flowF =     Frame(_left_margin, 2.0*cm,   _main_w,  19.85*cm,  id='flowL',     showBoundary=1)

        PageTemplate.__init__(self,
                              id='left',
                              frames=[latinF, hardyF, synF, otherF, calF, flowF], 
                              pagesize=A4)

    def beforeDrawPage(self, canv, doc):
        drawBorders(canv, _left_margin)

    def afterDrawPage(self, canv, doc): 
        drawFooter(canv, doc)


class RightPageTemplate(PageTemplate):
    def __init__(self):
        #allow a bigger margin on the left for binding
        latinF =    Frame(_right_margin, 27.5*cm,  _head_w,  0.8*cm,     id='latinR',    showBoundary=0, 
                          rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
        hardyF =    Frame(_w-1.1*cm, 26.05*cm,   cm,  cm,  id='hardR',       showBoundary=0, 
                          rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
        synF =      Frame(_right_margin+_budge, 26.65*cm,   _main_w,  0.55*cm,  id='synR',       showBoundary=0, 
                          rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
        calF =      Frame(_right_margin+_budge, 22.1*cm,   4.5*cm,  4.5*cm,  id='calR',       showBoundary=0, 
                          rightPadding=0, leftPadding=0, topPadding=0, bottomPadding=0)
        otherF =    Frame(_right_margin+5.1*cm, 22.1*cm,   12.4*cm,  4.5*cm,  id='otherR',       showBoundary=1)
        flowF =     Frame(_right_margin+_budge, 2.0*cm,   _main_w,  19.85*cm,  id='flowR',     showBoundary=1)

        PageTemplate.__init__(self,
                              id='right',
                              frames=[latinF, hardyF, synF, otherF, calF, flowF], 
                              pagesize=A4)

    def beforeDrawPage(self, canv, doc):
        drawBorders(canv, _right_margin)

    def afterDrawPage(self, canv, doc):
        drawFooter(canv, doc)


class MyDocTemplate(BaseDocTemplate):
    _invalidInitArgs = ('pageTemplates',)

    def __init__(self, filename, **kw):
        self.allowSplitting = 0
        BaseDocTemplate.__init__(self, filename, **kw)

        self.latinName = "(none initially)"
        self.latinWidth = 0 #(none initially)

    def afterInit(self):
        self._calc() #in case we have changed margin sizes etc

        self.leftMargin = _left_margin
        self.rightMargin = _right_margin
        self.topMargin = _top_margin
        self.bottomMargin = _bottom_margin
        self.width = _w - self.leftMargin - self.rightMargin
        self.height = _h - self.topMargin - self.bottomMargin

        frameStd = Frame(cm, self.bottomMargin, (_w - 2*cm), (_h - 3*cm), id='cvr', showBoundary=0)
        frameToC = Frame(self.rightMargin, self.bottomMargin, self.width, self.height, id='tocFrame', showBoundary=0)
        frameL = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='secLeftFrame', showBoundary=1)
        frameR = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='secRightFrame', showBoundary=1)

        self.addPageTemplates( [PageTemplate(id='Cover', frames=frameStd, onPage=coverPage, pagesize=self.pagesize), 
                                PageTemplate(id='ToC', frames=frameToC, onPage=tocPage, pagesize=self.pagesize), 
                                PageTemplate(id='blank', frames=frameStd, onPage=_doNothing, pagesize=self.pagesize), 
                                LeftPageTemplate(), 
                                RightPageTemplate(),
                                PageTemplate(id='secondary_left', frames=frameL, onPage=_doNothing, pagesize=self.pagesize),
                                PageTemplate(id='secondary_right', frames=frameR, onPage=_doNothing, pagesize=self.pagesize)
                               ] )

    def afterFlowable(self, flowable):
        """Registers ToC entries - and captures latin name for footer"""
        if isinstance(flowable, Paragraph):
            style = flowable.style.name
            key = None
            firstWord = style.split('_',1)[0]
            if (style == 'LatinName') or (style == 'LatinNameR') or (firstWord == 'LatinName'):
                level = 0
                key = 'latin-%s' % self.seq.nextf('LatinName')
                self.canv.bookmarkPage(key)

                wholeStr = flowable.getPlainText()
                if self.page % 2 == 0: #even numbers are on left pages
                    latinOnly = wholeStr.split('\xc2\xa0\xc2\xa0')[0] #looks for '&nbsp&nbsp' as divider
                else:
                    latinOnly = wholeStr.split('\xc2\xa0\xc2\xa0')[1]
                self.latinName = latinOnly
                E = [level, latinOnly, self.page]
                if key is not None: E.append(key)
                self.notify('TOCEntry', tuple(E))
                '''elif (style == 'CommonName'):
                self.commonName = flowable.getPlainText()
                self.commonWidth = stringWidth(self.commonName, styles['common'].fontName, styles['common'].fontSize)'''
            else:
                return


""" coverPage and otherPages are intended for non-flowing (i.e standard) parts of the pages """
def coverPage(canvas, doc):
    Title = "Plants for a Future"
    pageinfo = "The full database collected as a printable book"
    canvas.setTitle(Title + " : " + pageinfo)

    print "creating cover page..."
    canvas.saveState()
    d = Drawing(0,0)

    r = Rect( 0, 0, 12*cm, 4*cm, rx=5, ry=5 )
    r.strokeColor = colors.black
    r.fillColor = colors.white
    r.strokeWidth = 3
    d.add(r)
    d.drawOn(canvas, (_w/2.0)-6*cm, _h-(6.2*cm))

    canvas.setFont(stdFont, 30)
    canvas.drawCentredString(_w/2.0, _h-108, Title)
    canvas.setFont(stdItalic, 14)
    canvas.drawCentredString(_w/2.0, _h-150, pageinfo)
    canvas.restoreState()

def tocPage(canvas, doc):
    canvas.saveState()
    canvas.setFont(stdFont,10)
    canvas.drawCentredString((_w/2.0), 1.5*cm, "Table of Contents")
    canvas.restoreState()


def getMedicinal(plant):
    p = plant
    initial = p["Medicinal"]
    return initial


""" Run after 'Story' has been fully populated """
def go():
    doc = MyDocTemplate('result01.pdf')
    passes = doc.multiBuild(Story)

########################################################################

"""Build StyleSheet"""
styles = buildStyle()

h1 = PS(name = 'HeadingOne',
        fontName = stdFont, 
        fontSize = 14,
        leading = 16)

h2 = PS(name = 'HeadingTwo',
        fontName = stdFont, 
        fontSize = 12,
        leading = 14,
        leftIndent = 1*cm)

Story=[]

a = Story.append
a(NextPageTemplate('blank'))
a(PageBreak())
a(NextPageTemplate('ToC'))
a(PageBreak())

toc = TableOfContents()
toc.levelStyles = [ h1, h2 ]
a(toc)

a(NextPageTemplate('blank'))
a(PageBreak())


"""###LEFT PAGES SHOULD BE STYLED RIGHT-ALIGNED, AND RIGHT PAGES LEFT-ALIGNED###"""
#print type(plants)
for i, plant in enumerate(plants):
    ### THIS INITIAL CHECK BREAKS AS IT NEEDS TO BE BASED ON PAGE NUMBER, NOT PLANT NUMBER!!! ###
    if i %2 == 0: #IF FIRST PLANT APPEARS ON A RIGHTSIDE PAGE, ELSE REVERSE THE R and L
        page='R'
        template = 'right'
        second = 'left'
    else:
        page='L'
        template ='left'
        second = 'right'

    #FIRST THINGS FIRST:
    #Make sure the page templates flow nicely for each plant "chapter"
    a(NextPageTemplate([template, '*', ('secondary_'+template), ('secondary_'+second) ]))
    a(PageBreak())

    '''CAPTURE PLANT INFO IN OBJECTS'''
    p = plant

    '''for info in plant:
        print info, p[info]'''


    '''Header'''
    latin = p["Latin Name"]
    common = p["Common name"]
    family = p["Family"]
    syn = p["Synonyms"]
    """X. congestum. (Lour.)Merrill.  X. racemosum. Miq.  Apactis japonica.  Croton congestum.  
                Flacourtia japonica. Walp.  Hisingera japonica.  H. racemosa."""
    hardy = str(p["Hardyness"])

    '''Basic Info'''
    author = p["Author"]
    botanicalrefs = p["Botanical references"]
    width = p["Width"]
    height = p["Height"]
    habit = p["Habit"]

    planttype = clean("Deciduous/Evergreen", p)

    plantrange = p["Range"]
    habitat = p["Habitat"]
    soil = clean("Soil", plant)
    shade = p["Shade"]
    moisture = p["Moisture"]
    drained = p["Well-drained"]
    nf = p["Nitrogen fixer"]
    pH = p["pH"]
    acid = p["Acid"]
    alkaline = p["Alkaline"]
    saline = p["Saline"]
    wind = p["Wind"]

    rate = clean("Growth rate", plant)
    pollution = p["Pollution"]
    poorsoil = p["Poor soil"]
    drought = p["Drought"]
    heavyclay = p["Heavy clay"]
    tender = clean("FrostTender", plant)

    inleaf = p["In leaf"]
    flowering = p["Flowering time"]
    seedripens = p["Seed ripens"]
    flowertype = p["Flower Type"]
    pollinators = p["Pollinators"]
    selffertile = clean("Self-fertile", plant)

    hazards = p["Known hazards"]

    rating_edible = p["Rating"]
    rating_med = p["Medicinal Rating"]
    edibleuses = p["Edible uses"]
    medicinaluses = getMedicinal(plant)
    otheruses = p["Uses notes"]
    #the following encoding allows for special characters such as degree symbol
    cultivation = unicode(p["Cultivation details"], 'latin-1')#'ISO-8859-1')
    propagation = p["Propagation 1"]

    scented = p["Scented"] #boolean - requires further lookup in `ScentedPlants` table

    string = '''%s is %s %s growing to %gm by %gm at a %s rate.<br/> 
                It's habitats are %s <br/><br/> Range: %s
                    <br/><br/>
                Suitable for %s soils. <br/><br/>
                Shade: %s, Moisture: %s <br/>
                Well-drained: %d, Nitrogen fixer: %d <br/> ph: %s <br/>
                Acid: %d, Alkaline: %d, Saline: %d <br/>
                Wind: %s 
                    <br/><br/>
                Author: %s <br/> Botanical References: %s''' % (
                    latin, planttype, habit.lower(), width, height, rate, 
                    habitat[0].lower()+habitat[1:], plantrange, 
                    soil, shade, moisture, drained,
                    nf, pH, acid, alkaline, saline, wind, author, botanicalrefs )
    string = unicode(string, 'latin-1')

    latinW = stringWidth(latin, styles['latin'].fontName, styles['latin'].fontSize)
    commonW = stringWidth(common, styles['common'].fontName, styles['common'].fontSize)

    if (latinW + commonW + (_head_pad*3)) > _head_w:
        styleName = "LatinName_" + str(i)
        latinStyle = PS( name=styleName, 
                         parent=styles['Normal'],
                         fontName=titleFont, 
                         fontSize=_latinFontS, 
                         leading=22, 
                         spaceAfter=0)
        j = 1
        #should the latin string be too long, attempt to shrink until it fits
        while (latinW + commonW + (_head_pad*3)) > _head_w:
            #change the font size until ok...
            latinStyle.fontSize = _latinFontS -j
            latinW = stringWidth(latin, latinStyle.fontName, latinStyle.fontSize)
            j += 0.2
    else:
        latinStyle = styles['LatinName']

    if page == 'L':
        headerText = '''<para align="left">
                            %s
                            <font face="%s" size="%d">&nbsp;&nbsp;<super>%s</super></font>
                        </para>''' % (latin, subtitleFont, 12, common)
    else:
        headerText = '''<para align="right">
                            <font face="%s" size="%d"><super>%s</super>&nbsp;&nbsp;</font>
                            %s
                        </para>''' % (subtitleFont, 12, common, latin)
    latinPara = Paragraph(headerText, latinStyle)

    a(FrameBreak('latin'+page))
    a(latinPara)

    a(FrameBreak('syn'+page))
    a(KeepInFrame(_main_w, 1.5*cm, 
                  [Paragraph(syn, styles['syn'+page])], 
                  mode="shrink")) #can be shrink, truncate or overflow

    a(FrameBreak('hard'+page))
    a(Paragraph(hardy, styles['hardy']))

    a(FrameBreak('cal'+page))
    #SHALL BE ULTIMATELY POPULATED VIA DATABASE#
    greyOut = [ [0,0,1,1,1,1,1,0,0,0,0,0], [0,0,0,0,0,1,1,1,1,0,0,0], [0,0,0,0,0,0,0,0,1,1,1,0] ]

    cal = drawCalendar(greyOut)
    a(cal)

    a(FrameBreak('flow'+page))
    a(Paragraph(string, styles['Normal']))
    a(Paragraph("Edible Uses", styles['title']))
    a(Paragraph("Medicinal Uses", styles['title']))
    a(Paragraph("Other Uses", styles['title']))

    a(Paragraph("Cultivation", styles['title']))
    a(Paragraph(cultivation, styles['Normal']))

    a(Paragraph("Propagation", styles['title']))
    a(Paragraph(propagation, styles['Normal']))


##ASSEMBLE PDF###
go()

最佳答案

如果您只是在“左”模板和“右”模板之间切换,则可以尝试使用 BaseDocTemplate 类的 _handle_nextPageTemplate 方法。跟踪页码的一种方法是使用 afterPage Hook 来增加页码。

from reportlab.platypus import BaseDocTemplate

class MyDocTemplate(BaseDocTemplate):
    def __init__(self, *args, **kwargs):
        BaseDocTemplate.__init__(self, *args, **kwargs)
        self.__pageNum = 1

    def afterPage(self):
        """Called after all flowables have been drawn on a page"""

        # Increment pageNum since the page has been completed
        self.__pageNum += 1

        # If the page number is even, force "left-side" template
        if self.__pageNum % 2 == 0:
            self._handle_nextPageTemplate('left_template')
        else:
            self._handle_nextPageTemplate('right_template')

我尚未测试上面的代码,但您可能需要使用 beforePage 来代替,具体取决于它如何检查页面模板顺序。

关于python - ReportLab 变量 nextPageTemplate 的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14491169/

相关文章:

Python 将字节字符串直接打印到 Linux 上的打印机

python-3.x - ReportLab 带束带的包裹

python - 使用 reportlab 在 PDF 文件中创建渐变填充

ReportLab 和 pdfrw : Importing Scanned PDF

python - 是否有与 Python 的列表理解等效的 Scala?

python - 如何过滤阅读超过4本书的用户?

python - 来自 Python 正则表达式的 "Nothing to repeat"

python - 我可以让 PyInstaller 优化编译吗?

python - 如何在 Python 的 PDF 文档中包含 PDF 中的页面