ReportLab变量nextPageTemplate的
我正在把pfaf.org(未来植物)上的数据库转换成一本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循环中),我看不出解决办法,因为在那时我无法判断这株植物是否占用了多于一页,因此也不知道我当前处于哪一页。
我希望能够调整我自定义文档模板中的nextPageTemplate结构,但我不知道这是否可行。
这是可能的吗?还是有其他解决方案?我非常希望能得到帮助。我到处查阅资料,但找到的最佳例子似乎都没有涵盖这种情况。
如果有任何问题,请问我。谢谢。
谢谢你,Nitzle:
问题在于我不知道每种植物会占用多少页。例如,一种新植物从奇数页开始,所以我给它一个模板循环('右侧','*','次要左侧','次要右侧')。[次要页面只是一个带有适当边距的单一框架。]
如果那株植物只有一页,那就没问题,下一株植物会使用与上面相反的模板循环。但是,如果这株植物占用两页,那么下一株植物又会落在奇数页上,因此模板循环就不应该改变……我希望这样说能让你明白。
这就是我遇到的难题……如果我按照你说的做,就无法处理多页植物。我的大部分代码如下;我尝试把它简化了一些,希望仍然包含所有相关内容,而不是太多不必要的部分。
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 '  ' 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"> <super>%s</super></font>
</para>''' % (latin, subtitleFont, 12, common)
else:
headerText = '''<para align="right">
<font face="%s" size="%d"><super>%s</super> </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()
1 个回答
如果你只是想在“左侧”和“右侧”模板之间切换,可以试试使用 _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
来替代。