1
#Copyright ReportLab Europe Ltd. 2000-2004
2
#see license.txt for license details
3
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/doctemplate.py
5
__version__=''' $Id$ '''
8
This module contains the core structure of platypus.
10
Platypus constructs documents. Document styles are determined by DocumentTemplates.
12
Each DocumentTemplate contains one or more PageTemplates which defines the look of the
13
pages of the document.
15
Each PageTemplate has a procedure for drawing the "non-flowing" part of the page
16
(for example the header, footer, page number, fixed logo graphic, watermark, etcetera) and
17
a set of Frames which enclose the flowing part of the page (for example the paragraphs,
18
tables, or non-fixed diagrams of the text).
20
A document is built when a DocumentTemplate is fed a sequence of Flowables.
21
The action of the build consumes the flowables in order and places them onto
22
frames on pages as space allows. When a frame runs out of space the next frame
23
of the page is used. If no frame remains a new page is created. A new page
24
can also be created if a page break is forced.
26
The special invisible flowable NextPageTemplate can be used to specify
27
the page template for the next page (which by default is the one being used
28
for the current frame).
31
from reportlab.platypus.flowables import *
32
from reportlab.platypus.paragraph import Paragraph
33
from reportlab.platypus.frames import Frame
34
from reportlab.rl_config import defaultPageSize, verbose
35
import reportlab.lib.sequencer
40
class LayoutError(Exception):
43
def _doNothing(canvas, doc):
44
"Dummy callback for onPage"
48
class IndexingFlowable(Flowable):
49
"""Abstract interface definition for flowables which might
50
hold references to other pages or themselves be targets
51
of cross-references. XRefStart, XRefDest, Table of Contents,
56
def isSatisfied(self):
59
def notify(self, kind, stuff):
60
"""This will be called by the framework wherever 'stuff' happens.
61
'kind' will be a value that can be used to decide whether to
62
pay attention or not."""
65
def beforeBuild(self):
66
"""Called by multiBuild before it starts; use this to clear
71
"""Called after build ends but before isSatisfied"""
75
class ActionFlowable(Flowable):
76
'''This Flowable is never drawn, it can be used for data driven controls
77
For example to change a page template (from one column to two, for example)
78
use NextPageTemplate which creates an ActionFlowable.
80
def __init__(self,action=()):
81
if type(action) not in (ListType, TupleType):
83
self.action = tuple(action)
85
def wrap(self, availWidth, availHeight):
86
'''Should never be called.'''
87
raise NotImplementedError
90
'''Should never be called.'''
91
raise NotImplementedError
95
This is called by the doc.build processing to allow the instance to
96
implement its behaviour
98
action = self.action[0]
99
args = tuple(self.action[1:])
100
arn = 'handle_'+action
102
apply(getattr(doc,arn), args)
103
except AttributeError, aerr:
104
if aerr.args[0]==arn:
105
raise NotImplementedError, "Can't handle ActionFlowable(%s)" % action
109
t, v, unused = sys.exc_info()
110
raise t, "%s\n handle_%s args=%s"%(v,action,args)
115
def identity(self, maxLen=None):
116
return "ActionFlowable: %s" % str(self.action)
118
class NextFrameFlowable(ActionFlowable):
119
def __init__(self,ix,resume=0):
120
ActionFlowable.__init__(self,('nextFrame',ix,resume))
122
class CurrentFrameFlowable(ActionFlowable):
123
def __init__(self,ix,resume=0):
124
ActionFlowable.__init__(self,('currentFrame',ix,resume))
126
class _FrameBreak(ActionFlowable):
128
A special ActionFlowable that allows setting doc._nextFrameIndex
130
eg story.append(FrameBreak('mySpecialFrame'))
132
def __call__(self,ix=None,resume=0):
133
r = self.__class__(self.action+(resume,))
138
if getattr(self,'_ix',None): doc._nextFrameIndex = self._ix
139
ActionFlowable.apply(self,doc)
141
FrameBreak = _FrameBreak('frameEnd')
142
PageBegin = ActionFlowable('pageBegin')
144
def _evalMeasurement(n):
145
if type(n) is type(''):
146
from paraparser import _num
148
if type(n) is type(()): n = n[1]
151
class Indenter(ActionFlowable):
152
"""Increases or decreases left and right margins of frame.
154
This allows one to have a 'context-sensitive' indentation
155
and makes nested lists way easier.
158
def __init__(self, left=0, right=0):
159
self.left = _evalMeasurement(left)
160
self.right = _evalMeasurement(right)
162
def apply(self, doc):
163
doc.frame._leftExtraIndent = doc.frame._leftExtraIndent + self.left
164
doc.frame._rightExtraIndent = doc.frame._rightExtraIndent + self.right
167
class NextPageTemplate(ActionFlowable):
168
"""When you get to the next page, use the template specified (change to two column, for example) """
169
def __init__(self,pt):
170
ActionFlowable.__init__(self,('nextPageTemplate',pt))
175
essentially a list of Frames and an onPage routine to call at the start
176
of a page when this is selected. onPageEnd gets called at the end.
177
derived classes can also implement beforeDrawPage and afterDrawPage if they want
179
def __init__(self,id=None,frames=[],onPage=_doNothing, onPageEnd=_doNothing,
181
if type(frames) not in (ListType,TupleType): frames = [frames]
182
assert filter(lambda x: not isinstance(x,Frame), frames)==[], "frames argument error"
186
self.onPageEnd = onPageEnd
187
self.pagesize = pagesize
189
def beforeDrawPage(self,canv,doc):
190
"""Override this if you want additional functionality or prefer
191
a class based page routine. Called before any flowables for
192
this page are processed."""
195
def checkPageSize(self,canv,doc):
196
'''This gets called by the template framework
197
If canv size != template size then the canv size is set to
198
the template size or if that's not available to the
201
#### NEVER EVER EVER COMPARE FLOATS FOR EQUALITY
202
#RGB converting pagesizes to ints means we are accurate to one point
203
#RGB I suggest we should be aiming a little better
207
if canv._pagesize: cp = map(int, canv._pagesize)
208
if self.pagesize: sp = map(int, self.pagesize)
209
if doc.pagesize: dp = map(int, doc.pagesize)
212
canv.setPageSize(self.pagesize)
214
canv.setPageSize(doc.pagesize)
216
def afterDrawPage(self, canv, doc):
217
"""This is called after the last flowable for the page has
218
been processed. You might use this if the page header or
219
footer needed knowledge of what flowables were drawn on
224
class BaseDocTemplate:
226
First attempt at defining a document template class.
228
The basic idea is simple.
229
0) The document has a list of data associated with it
230
this data should derive from flowables. We'll have
231
special classes like PageBreak, FrameBreak to do things
232
like forcing a page end etc.
234
1) The document has one or more page templates.
236
2) Each page template has one or more frames.
238
3) The document class provides base methods for handling the
239
story events and some reasonable methods for getting the
240
story flowables into the frames.
242
4) The document instances can override the base handler routines.
244
Most of the methods for this class are not called directly by the user,
245
but in some advanced usages they may need to be overridden via subclassing.
247
EXCEPTION: doctemplate.build(...) must be called for most reasonable uses
248
since it builds a document using the page template.
250
Each document template builds exactly one document into a file specified
251
by the filename argument on initialization.
253
Possible keyword arguments for the initialization:
255
pageTemplates: A list of templates. Must be nonempty. Names
256
assigned to the templates are used for referring to them so no two used
257
templates should have the same name. For example you might want one template
258
for a title page, one for a section first page, one for a first page of
259
a chapter and two more for the interior of a chapter on odd and even pages.
260
If this argument is omitted then at least one pageTemplate should be provided
261
using the addPageTemplates method before the document is built.
262
pageSize: a 2-tuple or a size constant from reportlab/lib/pagesizes.pu.
263
Used by the SimpleDocTemplate subclass which does NOT accept a list of
264
pageTemplates but makes one for you; ignored when using pageTemplates.
266
showBoundary: if set draw a box around the frame boundaries.
270
bottomMargin: Margin sizes in points (default 1 inch)
271
These margins may be overridden by the pageTemplates. They are primarily of interest
272
for the SimpleDocumentTemplate subclass.
273
allowSplitting: If set flowables (eg, paragraphs) may be split across frames or pages
275
title: Internal title for document (does not automatically display on any page)
276
author: Internal author for document (does not automatically display on any page)
278
_initArgs = { 'pagesize':defaultPageSize,
290
_invalidInitArgs = ()
291
_firstPageTemplateIndex = 0
293
def __init__(self, filename, **kw):
294
"""create a document template bound to a filename (see class documentation for keyword arguments)"""
295
self.filename = filename
297
for k in self._initArgs.keys():
298
if not kw.has_key(k):
299
v = self._initArgs[k]
301
if k in self._invalidInitArgs:
302
raise ValueError, "Invalid argument %s" % k
305
#print "pagesize is", self.pagesize
307
p = self.pageTemplates
308
self.pageTemplates = []
309
self.addPageTemplates(p)
311
# facility to assist multi-build and cross-referencing.
312
# various hooks can put things into here - key is what
313
# you want, value is a page number. This can then be
314
# passed to indexing flowables.
316
self._indexingFlowables = []
319
#callback facility for progress monitoring
321
self._onProgress = None
322
self._flowableCount = 0 # so we know how far to go
325
#infinite loop detection if we start doing lots of empty pages
326
self._curPageFlowableCount = 0
328
self._emptyPagesAllowed = 10
330
#context sensitive margins - set by story, not from outside
331
self._leftExtraIndent = 0.0
332
self._rightExtraIndent = 0.0
338
self._rightMargin = self.pagesize[0] - self.rightMargin
339
self._topMargin = self.pagesize[1] - self.topMargin
340
self.width = self._rightMargin - self.leftMargin
341
self.height = self._topMargin - self.bottomMargin
343
def setPageCallBack(self, func):
344
'Simple progress monitor - func(pageNo) called on each new page'
347
def setProgressCallBack(self, func):
348
'''Cleverer progress monitor - func(typ, value) called regularly'''
349
self._onProgress = func
351
def clean_hanging(self):
352
'handle internal postponed actions'
353
while len(self._hanging):
354
self.handle_flowable(self._hanging)
356
def addPageTemplates(self,pageTemplates):
357
'add one or a sequence of pageTemplates'
358
if type(pageTemplates) not in (ListType,TupleType):
359
pageTemplates = [pageTemplates]
360
#this test below fails due to inconsistent imports!
361
#assert filter(lambda x: not isinstance(x,PageTemplate), pageTemplates)==[], "pageTemplates argument error"
362
for t in pageTemplates:
363
self.pageTemplates.append(t)
365
def handle_documentBegin(self):
366
'''implement actions at beginning of document'''
367
self._hanging = [PageBegin]
368
self.pageTemplate = self.pageTemplates[self._firstPageTemplateIndex]
370
self.beforeDocument()
372
def handle_pageBegin(self):
373
'''Perform actions required at beginning of page.
374
shouldn't normally be called directly'''
375
self.page = self.page + 1
376
self.pageTemplate.beforeDrawPage(self.canv,self)
377
self.pageTemplate.checkPageSize(self.canv,self)
378
self.pageTemplate.onPage(self.canv,self)
379
for f in self.pageTemplate.frames: f._reset()
381
#keep a count of flowables added to this page. zero indicates bad stuff
382
self._curPageFlowableCount = 0
383
if hasattr(self,'_nextFrameIndex'):
384
del self._nextFrameIndex
385
self.frame = self.pageTemplate.frames[0]
386
self.handle_frameBegin()
388
def handle_pageEnd(self):
389
''' show the current page
390
check the next page template
393
#detect infinite loops...
394
if self._curPageFlowableCount == 0:
395
self._emptyPages = self._emptyPages + 1
398
if self._emptyPages >= self._emptyPagesAllowed:
400
raise LayoutError("More than %d pages generated without content - halting layout. Likely that a flowable is too large for any frame." % self._emptyPagesAllowed)
402
pass #attempt to restore to good state
405
self._onProgress('PAGE', self.canv.getPageNumber())
406
self.pageTemplate.afterDrawPage(self.canv, self)
407
self.pageTemplate.onPageEnd(self.canv, self)
410
if hasattr(self,'_nextPageTemplateIndex'):
411
self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex]
412
del self._nextPageTemplateIndex
413
if self._emptyPages==0:
414
pass #store good state here
415
self._hanging.append(PageBegin)
417
def handle_pageBreak(self,slow=None):
418
'''some might choose not to end all the frames'''
419
if self._pageBreakQuick and not slow:
420
self.handle_pageEnd()
422
n = len(self._hanging)
423
while len(self._hanging)==n:
424
self.handle_frameEnd()
426
def handle_frameBegin(self,resume=0):
427
'''What to do at the beginning of a frame'''
430
if self.showBoundary or self.frame.showBoundary:
431
self.frame.drawBoundary(self.canv)
432
f._leftExtraIndent = self._leftExtraIndent
433
f._rightExtraIndent = self._rightExtraIndent
435
def handle_frameEnd(self,resume=0):
436
''' Handles the semantics of the end of a frame. This includes the selection of
437
the next frame or if this is the last frame then invoke pageEnd.
440
self._leftExtraIndent = self.frame._leftExtraIndent
441
self._rightExtraIndent = self.frame._rightExtraIndent
443
if hasattr(self,'_nextFrameIndex'):
444
frame = self.pageTemplate.frames[self._nextFrameIndex]
445
del self._nextFrameIndex
446
self.handle_frameBegin(resume)
447
elif hasattr(self.frame,'lastFrame') or self.frame is self.pageTemplate.frames[-1]:
448
self.handle_pageEnd()
452
self.frame = self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1]
453
self.handle_frameBegin()
455
def handle_nextPageTemplate(self,pt):
456
'''On endPage chenge to the page template with name or index pt'''
457
if type(pt) is StringType:
458
for t in self.pageTemplates:
460
self._nextPageTemplateIndex = self.pageTemplates.index(t)
462
raise ValueError, "can't find template('%s')"%pt
463
elif type(pt) is IntType:
464
self._nextPageTemplateIndex = pt
466
raise TypeError, "argument pt should be string or integer"
468
def handle_nextFrame(self,fx):
469
'''On endFrame chenge to the frame with name or index fx'''
470
if type(fx) is StringType:
471
for f in self.pageTemplate.frames:
473
self._nextFrameIndex = self.pageTemplate.frames.index(f)
475
raise ValueError, "can't find frame('%s')"%fx
476
elif type(fx) is IntType:
477
self._nextFrameIndex = fx
479
raise TypeError, "argument fx should be string or integer"
481
def handle_currentFrame(self,fx):
482
'''chenge to the frame with name or index fx'''
483
if type(fx) is StringType:
484
for f in self.pageTemplate.frames:
486
self._nextFrameIndex = self.pageTemplate.frames.index(f)
488
raise ValueError, "can't find frame('%s')"%fx
489
elif type(fx) is IntType:
490
self._nextFrameIndex = fx
492
raise TypeError, "argument fx should be string or integer"
494
def handle_breakBefore(self, flowables):
495
'''preprocessing step to allow pageBreakBefore and frameBreakBefore attributes'''
497
# if we insert a page break before, we'll process that, see it again,
498
# and go in an infinite loop. So we need to set a flag on the object
499
# saying 'skip me'. This should be unset on the next pass
500
if hasattr(first, '_skipMeNextTime'):
501
delattr(first, '_skipMeNextTime')
503
# this could all be made much quicker by putting the attributes
504
# in to the flowables with a defult value of 0
505
if hasattr(first,'pageBreakBefore') and first.pageBreakBefore == 1:
506
first._skipMeNextTime = 1
507
flowables.insert(0, PageBreak())
509
if hasattr(first,'style') and hasattr(first.style, 'pageBreakBefore') and first.style.pageBreakBefore == 1:
510
first._skipMeNextTime = 1
511
flowables.insert(0, PageBreak())
513
if hasattr(first,'frameBreakBefore') and first.frameBreakBefore == 1:
514
first._skipMeNextTime = 1
515
flowables.insert(0, FrameBreak())
517
if hasattr(first,'style') and hasattr(first.style, 'frameBreakBefore') and first.style.frameBreakBefore == 1:
518
first._skipMeNextTime = 1
519
flowables.insert(0, FrameBreak())
523
def handle_keepWithNext(self, flowables):
524
"implements keepWithNext"
527
while i<n and flowables[i].getKeepWithNext(): i = i + 1
530
K = KeepTogether(flowables[:i])
531
for f in K._flowables:
534
flowables.insert(0,K)
536
def handle_flowable(self,flowables):
537
'''try to handle one flowable from the front of list flowables.'''
539
#allow document a chance to look at, modify or ignore
540
#the object(s) about to be processed
541
self.filterFlowables(flowables)
543
self.handle_breakBefore(flowables)
544
self.handle_keepWithNext(flowables)
546
#print 'handling flowable %s' % f.identity()
551
if isinstance(f,PageBreak):
552
if isinstance(f,SlowPageBreak):
553
self.handle_pageBreak(slow=1)
555
self.handle_pageBreak()
556
self.afterFlowable(f)
557
elif isinstance(f,ActionFlowable):
559
self.afterFlowable(f)
561
#try to fit it then draw it
562
if self.frame.add(f, self.canv, trySplit=self.allowSplitting):
563
self._curPageFlowableCount = self._curPageFlowableCount + 1
564
self.afterFlowable(f)
566
#if isinstance(f, KeepTogether): print 'could not add it to frame'
567
if self.allowSplitting:
568
# see if this is a splittable thing
569
S = self.frame.split(f,self.canv)
570
#print '%d parts to sequence on page %d' % (len(S), self.page)
574
#if isinstance(f, KeepTogether): print 'n=%d' % n
576
if self.frame.add(S[0], self.canv, trySplit=0):
577
self._curPageFlowableCount = self._curPageFlowableCount + 1
578
self.afterFlowable(S[0])
580
raise LayoutError("Splitting error(n==%d) on page %d in\n%s" % (n,self.page,f.identity(30)))
582
for f in xrange(n-1):
583
flowables.insert(f,S[f]) # put split flowables back on the list
585
if hasattr(f,'_postponed'):
586
raise LayoutError("Flowable %s too large on page %d" % (f.identity(30), self.page))
587
# this ought to be cleared when they are finally drawn!
589
flowables.insert(0,f) # put the flowable back
590
self.handle_frameEnd()
592
#these are provided so that deriving classes can refer to them
593
_handle_documentBegin = handle_documentBegin
594
_handle_pageBegin = handle_pageBegin
595
_handle_pageEnd = handle_pageEnd
596
_handle_frameBegin = handle_frameBegin
597
_handle_frameEnd = handle_frameEnd
598
_handle_flowable = handle_flowable
599
_handle_nextPageTemplate = handle_nextPageTemplate
600
_handle_currentFrame = handle_currentFrame
601
_handle_nextFrame = handle_nextFrame
603
def _startBuild(self, filename=None, canvasmaker=canvas.Canvas):
605
self.canv = canvasmaker(filename or self.filename,
606
pagesize=self.pagesize,
607
invariant=self.invariant)
609
self.canv.setPageCallBack(self._onPage)
610
self.handle_documentBegin()
613
if self._hanging!=[] and self._hanging[-1] is PageBegin:
614
del self._hanging[-1]
618
self.handle_pageBreak()
620
if getattr(self,'_doSave',1): self.canv.save()
621
if self._onPage: self.canv.setPageCallBack(None)
623
def build(self, flowables, filename=None, canvasmaker=canvas.Canvas):
624
"""Build the document from a list of flowables.
625
If the filename argument is provided then that filename is used
626
rather than the one provided upon initialization.
627
If the canvasmaker argument is provided then it will be used
628
instead of the default. For example a slideshow might use
629
an alternate canvas which places 6 slides on a page (by
630
doing translations, scalings and redefining the page break
633
#assert filter(lambda x: not isinstance(x,Flowable), flowables)==[], "flowables argument error"
634
flowableCount = len(flowables)
636
self._onProgress('STARTED',0)
637
self._onProgress('SIZE_EST', len(flowables))
638
self._startBuild(filename,canvasmaker)
640
while len(flowables):
644
self.handle_flowable(flowables)
646
#if it has trace info, add it to the traceback message.
647
if hasattr(first, '_traceInfo') and first._traceInfo:
648
exc = sys.exc_info()[1]
649
args = list(exc.args)
650
tr = first._traceInfo
651
args[0] = args[0] + '\n(srcFile %s, line %d char %d to line %d char %d)' % (
658
exc.args = tuple(args)
661
self._onProgress('PROGRESS',flowableCount - len(flowables))
665
self._onProgress('FINISHED',0)
667
def _allSatisfied(self):
668
"""Called by multi-build - are all cross-references resolved?"""
670
for f in self._indexingFlowables:
671
if not f.isSatisfied():
676
def notify(self, kind, stuff):
677
""""Forward to any listeners"""
678
for l in self._indexingFlowables:
679
l.notify(kind, stuff)
681
def pageRef(self, label):
682
"""hook to register a page number"""
683
if verbose: print "pageRef called with label '%s' on page %d" % (
685
self._pageRefs[label] = self.page
687
def multiBuild(self, story,
689
canvasmaker=canvas.Canvas,
691
"""Makes multiple passes until all indexing flowables
693
self._indexingFlowables = []
694
#scan the story and keep a copy
696
if thing.isIndexing():
697
self._indexingFlowables.append(thing)
698
#print 'scanned story, found these indexing flowables:\n'
699
#print self._indexingFlowables
701
#better fix for filename is a 'file' problem
707
self.onProgress('PASS', passes)
708
if verbose: print 'building pass '+str(passes) + '...',
710
for fl in self._indexingFlowables:
713
# work with a copy of the story, since it is consumed
715
self.build(tempStory, filename, canvasmaker)
716
#self.notify('debug',None)
718
#clean up so multi-build does not go wrong - the frame
719
#packer might have tacked an attribute onto some flowables
721
if hasattr(elem, '_postponed'):
724
for fl in self._indexingFlowables:
727
happy = self._allSatisfied()
733
if passes > maxPasses:
734
raise IndexError, "Index entries not resolved after %d passes" % maxPasses
736
if verbose: print 'saved'
738
#these are pure virtuals override in derived classes
739
#NB these get called at suitable places by the base class
740
#so if you derive and override the handle_xxx methods
741
#it's up to you to ensure that they maintain the needed consistency
743
"""This is called after initialisation of the base class."""
746
def beforeDocument(self):
747
"""This is called before any processing is
748
done on the document."""
751
def beforePage(self):
752
"""This is called at the beginning of page
753
processing, and immediately before the
754
beforeDrawPage method of the current page
759
"""This is called after page processing, and
760
immediately after the afterDrawPage method
761
of the current page template."""
764
def filterFlowables(self,flowables):
765
'''called to filter flowables at the start of the main handle_flowable method.
766
Upon return if flowables[0] has been set to None it is discarded and the main
771
def afterFlowable(self, flowable):
772
'''called after a flowable has been rendered'''
776
class SimpleDocTemplate(BaseDocTemplate):
777
"""A special case document template that will handle many simple documents.
778
See documentation for BaseDocTemplate. No pageTemplates are required
779
for this special case. A page templates are inferred from the
780
margin information and the onFirstPage, onLaterPages arguments to the build method.
782
A document which has all pages with the same look except for the first
783
page may can be built using this special approach.
785
_invalidInitArgs = ('pageTemplates',)
787
def handle_pageBegin(self):
788
'''override base method to add a change of page template after the firstpage.
790
self._handle_pageBegin()
791
self._handle_nextPageTemplate('Later')
793
def build(self,flowables,onFirstPage=_doNothing, onLaterPages=_doNothing):
794
"""build the document using the flowables. Annotate the first page using the onFirstPage
795
function and later pages using the onLaterPages function. The onXXX pages should follow
798
def myOnFirstPage(canvas, document):
799
# do annotations and modify the document
802
The functions can do things like draw logos, page numbers,
803
footers, etcetera. They can use external variables to vary
804
the look (for example providing page numbering or section names).
806
self._calc() #in case we changed margins sizes etc
807
frameT = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='normal')
808
self.addPageTemplates([PageTemplate(id='First',frames=frameT, onPage=onFirstPage,pagesize=self.pagesize),
809
PageTemplate(id='Later',frames=frameT, onPage=onLaterPages,pagesize=self.pagesize)])
810
if onFirstPage is _doNothing and hasattr(self,'onFirstPage'):
811
self.pageTemplates[0].beforeDrawPage = self.onFirstPage
812
if onLaterPages is _doNothing and hasattr(self,'onLaterPages'):
813
self.pageTemplates[1].beforeDrawPage = self.onLaterPages
814
BaseDocTemplate.build(self,flowables)
817
def progressCB(typ, value):
818
"""Example prototype for progress monitoring.
820
This aims to provide info about what is going on
821
during a big job. It should enable, for example, a reasonably
822
smooth progress bar to be drawn. We design the argument
823
signature to be predictable and conducive to programming in
824
other (type safe) languages. If set, this will be called
825
repeatedly with pairs of values. The first is a string
826
indicating the type of call; the second is a numeric value.
828
typ 'STARTING', value = 0
829
typ 'SIZE_EST', value = numeric estimate of job size
830
typ 'PASS', value = number of this rendering pass
831
typ 'PROGRESS', value = number between 0 and SIZE_EST
832
typ 'PAGE', value = page number of page
833
type 'FINISHED', value = 0
836
STARTING - always called once
837
SIZE_EST - always called once
838
PROGRESS - called often
839
PAGE - called often when page is emitted
840
FINISHED - called when really, really finished
842
some juggling is needed to accurately estimate numbers of
843
pages in pageDrawing mode.
845
NOTE: the SIZE_EST is a guess. It is possible that the
846
PROGRESS value may slightly exceed it, or may even step
847
back a little on rare occasions. The only way to be
848
really accurate would be to do two passes, and I don't
849
want to take that performance hit.
851
print 'PROGRESS MONITOR: %-10s %d' % (typ, value)
853
if __name__ == '__main__':
855
def myFirstPage(canvas, doc):
857
canvas.setStrokeColor(red)
858
canvas.setLineWidth(5)
859
canvas.line(66,72,66,PAGE_HEIGHT-72)
860
canvas.setFont('Times-Bold',24)
861
canvas.drawString(108, PAGE_HEIGHT-108, "TABLE OF CONTENTS DEMO")
862
canvas.setFont('Times-Roman',12)
863
canvas.drawString(4 * inch, 0.75 * inch, "First Page")
864
canvas.restoreState()
866
def myLaterPages(canvas, doc):
868
canvas.setStrokeColor(red)
869
canvas.setLineWidth(5)
870
canvas.line(66,72,66,PAGE_HEIGHT-72)
871
canvas.setFont('Times-Roman',12)
872
canvas.drawString(4 * inch, 0.75 * inch, "Page %d" % doc.page)
873
canvas.restoreState()
877
from reportlab.lib.styles import ParagraphStyle
878
#from paragraph import Paragraph
879
from doctemplate import SimpleDocTemplate
882
normal = ParagraphStyle('normal')
883
normal.firstLineIndent = 18
884
normal.spaceBefore = 6
885
from reportlab.lib.randomtext import randomText
888
height = 0.5 + (2*random.random())
889
box = XBox(6 * inch, height * inch, 'Box Number %d' % i)
890
objects_to_draw.append(box)
891
para = Paragraph(randomText(), normal)
892
objects_to_draw.append(para)
894
SimpleDocTemplate('doctemplate.pdf').build(objects_to_draw,
895
onFirstPage=myFirstPage,onLaterPages=myLaterPages)