2

我正在将数据库从 pfaf.org(面向未来的植物)转换为 pdf 书。关于pageTemplates,我遇到了一些绊脚石。

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

目前处理如下(例如):

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 结构,但我不知道这是否可能。

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

有任何问题,请提出。谢谢


谢谢你,Nitzle:
问题是我不知道每株植物会占用多少页。例如 - 一个新工厂从一个奇怪的页面开始,所以我给它一个模板循环('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()
4

1 回答 1

3

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

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它来代替它检查页面模板顺序的方式。

于 2013-01-25T14:49:46.583 回答