1#Copyright ReportLab Europe Ltd. 2000-2017
2#see license.txt for license details
3#history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/platypus/doctemplate.py
4__all__ = (
5        'ActionFlowable',
6        'BaseDocTemplate',
7        'CurrentFrameFlowable',
8        'FrameActionFlowable',
9        'FrameBreak',
10        'Indenter',
11        'IndexingFlowable',
12        'LayoutError',
13        'LCActionFlowable',
14        'NextFrameFlowable',
15        'NextPageTemplate',
16        'NotAtTopPageBreak',
17        'NullActionFlowable',
18        'PageAccumulator',
19        'PageBegin',
20        'PageTemplate',
21        'SimpleDocTemplate',
22        )
23__version__='3.5.20'
24
25__doc__="""
26This module contains the core structure of platypus.
27
28rlatypus constructs documents.  Document styles are determined by DocumentTemplates.
29
30Each DocumentTemplate contains one or more PageTemplates which defines the look of the
31pages of the document.
32
33Each PageTemplate has a procedure for drawing the "non-flowing" part of the page
34(for example the header, footer, page number, fixed logo graphic, watermark, etcetera) and
35a set of Frames which enclose the flowing part of the page (for example the paragraphs,
36tables, or non-fixed diagrams of the text).
37
38A document is built when a DocumentTemplate is fed a sequence of Flowables.
39The action of the build consumes the flowables in order and places them onto
40frames on pages as space allows.  When a frame runs out of space the next frame
41of the page is used.  If no frame remains a new page is created.  A new page
42can also be created if a page break is forced.
43
44The special invisible flowable NextPageTemplate can be used to specify
45the page template for the next page (which by default is the one being used
46for the current frame).
47"""
48
49from reportlab.platypus.flowables import *
50from reportlab.platypus.flowables import _ContainerSpace
51from reportlab.lib.units import inch
52from reportlab.platypus.paragraph import Paragraph
53from reportlab.platypus.frames import Frame
54from reportlab.rl_config import defaultPageSize, verbose
55import reportlab.lib.sequencer
56from reportlab.pdfgen import canvas
57from reportlab.lib.utils import isSeq, encode_label, decode_label, annotateException, strTypes
58from reportlab import ascii
59
60try:
61    set
62except NameError:
63    from sets import Set as set
64
65import sys
66import logging
67logger = logging.getLogger("reportlab.platypus")
68
69class LayoutError(Exception):
70    pass
71
72def _fSizeString(f):
73    #used to get size during error messages
74    w=getattr(f,'width',None)
75    if w is None:
76        w=getattr(f,'_width',None)
77
78    h=getattr(f,'height',None)
79    if h is None:
80        h=getattr(f,'_height',None)
81    #tables in particular may have some nasty large culprit
82    if hasattr(f, '_culprit'):
83        c = ', %s, ' % f._culprit()
84    else:
85        c = ''
86    if w is not None or h is not None:
87        if w is None: w='???'
88        if h is None: h='???'
89        return '(%s x %s)%s' % (w,h,c)
90    return ''
91
92def _doNothing(canvas, doc):
93    "Dummy callback for onPage"
94    pass
95
96class PTCycle(list):
97    def __new__(cls,*args,**kwds):
98        self = list.__new__(cls,*args,**kwds)
99        self._restart = 0
100        self._idx = 0
101        return self
102
103    @property
104    def next_value(self):
105        v = self[self._idx]
106        self._idx += 1
107        if self._idx>=len(self):
108            self._idx = self._restart
109        return v
110
111    @property
112    def peek(self):
113        return self[self._idx]
114
115class IndexingFlowable(Flowable):
116    """Abstract interface definition for flowables which might
117    hold references to other pages or themselves be targets
118    of cross-references.  XRefStart, XRefDest, Table of Contents,
119    Indexes etc."""
120    def isIndexing(self):
121        return 1
122
123    def isSatisfied(self):
124        return 1
125
126    def notify(self, kind, stuff):
127        """This will be called by the framework wherever 'stuff' happens.
128        'kind' will be a value that can be used to decide whether to
129        pay attention or not."""
130        pass
131
132    def beforeBuild(self):
133        """Called by multiBuild before it starts; use this to clear
134        old contents"""
135        pass
136
137    def afterBuild(self):
138        """Called after build ends but before isSatisfied"""
139        pass
140
141class ActionFlowable(Flowable):
142    '''This Flowable is never drawn, it can be used for data driven controls
143       For example to change a page template (from one column to two, for example)
144       use NextPageTemplate which creates an ActionFlowable.
145    '''
146    def __init__(self,action=()):
147        #must call super init to ensure it has a width and height (of zero),
148        #as in some cases the packer might get called on it...
149        Flowable.__init__(self)
150        if not isSeq(action):
151            action = (action,)
152        self.action = tuple(action)
153
154    def apply(self,doc):
155        '''
156        This is called by the doc.build processing to allow the instance to
157        implement its behaviour
158        '''
159        action = self.action[0]
160        args = tuple(self.action[1:])
161        arn = 'handle_'+action
162        if arn=="handle_nextPageTemplate" and args[0]=='main':
163            pass
164        try:
165            getattr(doc,arn)(*args)
166        except AttributeError as aerr:
167            if aerr.args[0]==arn:
168                raise NotImplementedError("Can't handle ActionFlowable(%s)" % action)
169            else:
170                raise
171        except:
172            annotateException("\nhandle_%s args=%s"%(action,ascii(args)))
173
174    def __call__(self):
175        return self
176
177    def identity(self, maxLen=None):
178        return "ActionFlowable: %s%s" % (str(self.action),self._frameName())
179
180class NullActionFlowable(ActionFlowable):
181    '''an ActionFlowable that does nothing'''
182    def apply(self,doc):
183        pass
184
185class LCActionFlowable(ActionFlowable):
186    locChanger = 1                  #we cause a frame or page change
187
188    def wrap(self, availWidth, availHeight):
189        '''Should never be called.'''
190        raise NotImplementedError('%s.wrap should never be called' % self.__class__.__name__)
191
192    def draw(self):
193        '''Should never be called.'''
194        raise NotImplementedError('%s.draw should never be called' % self.__class__.__name__)
195
196class NextFrameFlowable(ActionFlowable):
197    locChanger = 1                  #we cause a frame or page change
198    def __init__(self,ix,resume=0):
199        ActionFlowable.__init__(self,('nextFrame',ix,resume))
200
201class CurrentFrameFlowable(LCActionFlowable):
202    def __init__(self,ix,resume=0):
203        ActionFlowable.__init__(self,('currentFrame',ix,resume))
204
205class NullActionFlowable(ActionFlowable):
206    def apply(self,doc):
207        pass
208
209class _FrameBreak(LCActionFlowable):
210    '''
211    A special ActionFlowable that allows setting doc._nextFrameIndex
212
213    eg story.append(FrameBreak('mySpecialFrame'))
214    '''
215    def __call__(self,ix=None,resume=0):
216        r = self.__class__(self.action+(resume,))
217        r._ix = ix
218        return r
219
220    def apply(self,doc):
221        if getattr(self,'_ix',None):
222            doc.handle_nextFrame(self._ix)
223        ActionFlowable.apply(self,doc)
224
225FrameBreak = _FrameBreak('frameEnd')
226PageBegin = LCActionFlowable('pageBegin')
227
228def _evalMeasurement(n):
229    if isinstance(n,str):
230        from reportlab.platypus.paraparser import _num
231        n = _num(n)
232        if isSeq(n): n = n[1]
233    return n
234
235class FrameActionFlowable(Flowable):
236    _fixedWidth = _fixedHeight = 1
237    def __init__(self,*arg,**kw):
238        raise NotImplementedError('%s.__init__ should never be called for abstract Class'%self.__class__.__name__)
239
240    def frameAction(self,frame):
241        raise NotImplementedError('%s.frameAction should never be called for abstract Class'%self.__class__.__name__)
242
243class Indenter(FrameActionFlowable):
244    """Increases or decreases left and right margins of frame.
245
246    This allows one to have a 'context-sensitive' indentation
247    and makes nested lists way easier.
248    """
249    _ZEROSIZE=True
250    width=0
251    height=0
252    def __init__(self, left=0, right=0):
253        self.left = _evalMeasurement(left)
254        self.right = _evalMeasurement(right)
255
256    def frameAction(self, frame):
257        frame._leftExtraIndent += self.left
258        frame._rightExtraIndent += self.right
259
260class NotAtTopPageBreak(FrameActionFlowable):
261    locChanger = 1                  #we cause a frame or page change
262    def __init__(self,nextTemplate=None):
263        self.nextTemplate = nextTemplate
264
265    def frameAction(self,frame):
266        if not frame._atTop:
267            frame.add_generated_content(PageBreak(nextTemplate=self.nextTemplate))
268
269class NextPageTemplate(ActionFlowable):
270    locChanger = 1                  #we cause a frame or page change
271    """When you get to the next page, use the template specified (change to two column, for example)  """
272    def __init__(self,pt):
273        ActionFlowable.__init__(self,('nextPageTemplate',pt))
274
275class PageTemplate:
276    """
277    essentially a list of Frames and an onPage routine to call at the start
278    of a page when this is selected. onPageEnd gets called at the end.
279    derived classes can also implement beforeDrawPage and afterDrawPage if they want
280    """
281    def __init__(self,id=None,frames=[],onPage=_doNothing, onPageEnd=_doNothing,
282                 pagesize=None, autoNextPageTemplate=None,
283                 cropBox=None,
284                 artBox=None,
285                 trimBox=None,
286                 bleedBox=None,
287                 ):
288        frames = frames or []
289        if not isSeq(frames): frames = [frames]
290        assert [x for x in frames if not isinstance(x,Frame)]==[], "frames argument error"
291        self.id = id
292        self.frames = frames
293        self.onPage = onPage
294        self.onPageEnd = onPageEnd
295        self.pagesize = pagesize
296        self.autoNextPageTemplate = autoNextPageTemplate
297        self.cropBox = cropBox
298        self.artBox = artBox
299        self.trimBox = trimBox
300        self.bleedBox = bleedBox
301
302    def beforeDrawPage(self,canv,doc):
303        """Override this if you want additional functionality or prefer
304        a class based page routine.  Called before any flowables for
305        this page are processed."""
306        pass
307
308    def checkPageSize(self,canv,doc):
309        """This gets called by the template framework
310        If canv size != template size then the canv size is set to
311        the template size or if that's not available to the
312        doc size.
313        """
314        #### NEVER EVER EVER COMPARE FLOATS FOR EQUALITY
315        #RGB converting pagesizes to ints means we are accurate to one point
316        #RGB I suggest we should be aiming a little better
317        cp = None
318        dp = None
319        sp = None
320        if canv._pagesize: cp = list(map(int, canv._pagesize))
321        if self.pagesize: sp = list(map(int, self.pagesize))
322        if doc.pagesize: dp = list(map(int, doc.pagesize))
323        if cp!=sp:
324            if sp:
325                canv.setPageSize(self.pagesize)
326            elif cp!=dp:
327                canv.setPageSize(doc.pagesize)
328        for box in 'crop','art','trim','bleed':
329            size = getattr(self,box+'Box',None)
330            if size:
331                canv.setCropBox(size,name=box)
332
333    def afterDrawPage(self, canv, doc):
334        """This is called after the last flowable for the page has
335        been processed.  You might use this if the page header or
336        footer needed knowledge of what flowables were drawn on
337        this page."""
338        pass
339
340def _addGeneratedContent(flowables,frame):
341    S = getattr(frame,'_generated_content',None)
342    if S:
343        flowables[0:0] = S
344        del frame._generated_content
345
346class onDrawStr(str):
347    def __new__(cls,value,onDraw,label,kind=None):
348        self = str.__new__(cls,value)
349        self.onDraw = onDraw
350        self.kind = kind
351        self.label = label
352        return self
353
354class PageAccumulator:
355    '''gadget to accumulate information in a page
356    and then allow it to be interrogated at the end
357    of the page'''
358    _count = 0
359    def __init__(self,name=None):
360        if name is None:
361            name = self.__class__.__name__+str(self.__class__._count)
362            self.__class__._count += 1
363        self.name = name
364        self.data = []
365
366    def reset(self):
367        self.data[:] = []
368
369    def add(self,*args):
370        self.data.append(args)
371
372    def onDrawText(self,*args):
373        return '<onDraw name="%s" label="%s" />' % (self.name,encode_label(args))
374
375    def __call__(self,canv,kind,label):
376        self.add(*decode_label(label))
377
378    def attachToPageTemplate(self,pt):
379        if pt.onPage:
380            def onPage(canv,doc,oop=pt.onPage):
381                self.onPage(canv,doc)
382                oop(canv,doc)
383        else:
384            def onPage(canv,doc):
385                self.onPage(canv,doc)
386        pt.onPage = onPage
387        if pt.onPageEnd:
388            def onPageEnd(canv,doc,oop=pt.onPageEnd):
389                self.onPageEnd(canv,doc)
390                oop(canv,doc)
391        else:
392            def onPageEnd(canv,doc):
393                self.onPageEnd(canv,doc)
394        pt.onPageEnd = onPageEnd
395
396    def onPage(self,canv,doc):
397        '''this will be called at the start of the page'''
398        setattr(canv,self.name,self)    #push ourselves onto the canvas
399        self.reset()
400
401    def onPageEnd(self,canv,doc):
402        '''this will be called at the end of a page'''
403        self.pageEndAction(canv,doc)
404        try:
405            delattr(canv,self.name)
406        except:
407            pass
408        self.reset()
409
410    def pageEndAction(self,canv,doc):
411        '''this should be overridden to do something useful'''
412        pass
413
414    def onDrawStr(self,value,*args):
415        return onDrawStr(value,self,encode_label(args))
416
417def _ktAllow(f):
418    '''return true if allowed in containers like KeepTogether'''
419    return not (isinstance(f,(_ContainerSpace,DocIf,DocWhile)) or getattr(f,'locChanger',False))
420
421class BaseDocTemplate:
422    """
423    First attempt at defining a document template class.
424
425    The basic idea is simple.
426
427    1)  The document has a list of data associated with it
428        this data should derive from flowables. We'll have
429        special classes like PageBreak, FrameBreak to do things
430        like forcing a page end etc.
431
432    2)  The document has one or more page templates.
433
434    3)  Each page template has one or more frames.
435
436    4)  The document class provides base methods for handling the
437        story events and some reasonable methods for getting the
438        story flowables into the frames.
439
440    5)  The document instances can override the base handler routines.
441
442    Most of the methods for this class are not called directly by the user,
443    but in some advanced usages they may need to be overridden via subclassing.
444
445    EXCEPTION: doctemplate.build(...) must be called for most reasonable uses
446    since it builds a document using the page template.
447
448    Each document template builds exactly one document into a file specified
449    by the filename argument on initialization.
450
451    Possible keyword arguments for the initialization:
452
453    - pageTemplates: A list of templates.  Must be nonempty.  Names
454      assigned to the templates are used for referring to them so no two used
455      templates should have the same name.  For example you might want one template
456      for a title page, one for a section first page, one for a first page of
457      a chapter and two more for the interior of a chapter on odd and even pages.
458      If this argument is omitted then at least one pageTemplate should be provided
459      using the addPageTemplates method before the document is built.
460    - pageSize: a 2-tuple or a size constant from reportlab/lib/pagesizes.pu.
461      Used by the SimpleDocTemplate subclass which does NOT accept a list of
462      pageTemplates but makes one for you; ignored when using pageTemplates.
463
464    - showBoundary: if set draw a box around the frame boundaries.
465    - leftMargin:
466    - rightMargin:
467    - topMargin:
468    - bottomMargin:  Margin sizes in points (default 1 inch).  These margins may be
469      overridden by the pageTemplates.  They are primarily of interest for the
470      SimpleDocumentTemplate subclass.
471
472    - allowSplitting:  If set flowables (eg, paragraphs) may be split across frames or pages
473      (default: 1)
474    - title: Internal title for document (does not automatically display on any page)
475    - author: Internal author for document (does not automatically display on any page)
476    """
477    _initArgs = {   'pagesize':defaultPageSize,
478                    'pageTemplates':[],
479                    'showBoundary':0,
480                    'leftMargin':inch,
481                    'rightMargin':inch,
482                    'topMargin':inch,
483                    'bottomMargin':inch,
484                    'allowSplitting':1,
485                    'title':None,
486                    'author':None,
487                    'subject':None,
488                    'creator':None,
489                    'producer':None,
490                    'keywords':[],
491                    'invariant':None,
492                    'pageCompression':None,
493                    '_pageBreakQuick':1,
494                    'rotation':0,
495                    '_debug':0,
496                    'encrypt': None,
497                    'cropMarks': None,
498                    'enforceColorSpace': None,
499                    'displayDocTitle': None,
500                    'lang': None,
501                    'initialFontName': None,
502                    'initialFontSize': None,
503                    'initialLeading': None,
504                    'cropBox': None,
505                    'artBox': None,
506                    'trimBox': None,
507                    'bleedBox': None,
508                    'keepTogetherClass': KeepTogether,
509                    'hideToolbar': None,
510                    'hideMenubar': None,
511                    'hideWindowUI': None,
512                    'fitWindow': None,
513                    'centerWindow': None,
514                    'nonFullScreenPageMode': None,
515                    'direction': None,
516                    'viewArea': None,
517                    'viewClip': None,
518                    'printArea': None,
519                    'printClip': None,
520                    'printScaling': None,
521                    'duplex': None,
522                    }
523    _invalidInitArgs = ()
524    _firstPageTemplateIndex = 0
525
526    def __init__(self, filename, **kw):
527        """create a document template bound to a filename (see class documentation for keyword arguments)"""
528        self.filename = filename
529        self._nameSpace = dict(doc=self)
530        self._lifetimes = {}
531
532        for k in self._initArgs.keys():
533            if k not in kw:
534                v = self._initArgs[k]
535            else:
536                if k in self._invalidInitArgs:
537                    raise ValueError("Invalid argument %s" % k)
538                v = kw[k]
539            setattr(self,k,v)
540
541        p = self.pageTemplates
542        self.pageTemplates = []
543        self.addPageTemplates(p)
544
545        # facility to assist multi-build and cross-referencing.
546        # various hooks can put things into here - key is what
547        # you want, value is a page number.  This can then be
548        # passed to indexing flowables.
549        self._pageRefs = {}
550        self._indexingFlowables = []
551
552        #callback facility for progress monitoring
553        self._onPage = None
554        self._onProgress = None
555        self._flowableCount = 0  # so we know how far to go
556
557        #infinite loop detection if we start doing lots of empty pages
558        self._curPageFlowableCount = 0
559        self._emptyPages = 0
560        self._emptyPagesAllowed = 10
561
562        #context sensitive margins - set by story, not from outside
563        self._leftExtraIndent = 0.0
564        self._rightExtraIndent = 0.0
565        self._topFlowables = []
566        self._pageTopFlowables = []
567        self._frameBGs = []
568
569        self._calc()
570        self.afterInit()
571
572    def _calc(self):
573        self._rightMargin = self.pagesize[0] - self.rightMargin
574        self._topMargin = self.pagesize[1] - self.topMargin
575        self.width = self._rightMargin - self.leftMargin
576        self.height = self._topMargin - self.bottomMargin
577
578    def setPageCallBack(self, func):
579        'Simple progress monitor - func(pageNo) called on each new page'
580        self._onPage = func
581
582    def setProgressCallBack(self, func):
583        '''Cleverer progress monitor - func(typ, value) called regularly'''
584        self._onProgress = func
585
586    def clean_hanging(self):
587        'handle internal postponed actions'
588        while len(self._hanging):
589            self.handle_flowable(self._hanging)
590
591    def addPageTemplates(self,pageTemplates):
592        'add one or a sequence of pageTemplates'
593        if not isSeq(pageTemplates):
594            pageTemplates = [pageTemplates]
595        #this test below fails due to inconsistent imports!
596        #assert filter(lambda x: not isinstance(x,PageTemplate), pageTemplates)==[], "pageTemplates argument error"
597        for t in pageTemplates:
598            self.pageTemplates.append(t)
599
600    def handle_documentBegin(self):
601        '''implement actions at beginning of document'''
602        self._hanging = [PageBegin]
603        if isinstance(self._firstPageTemplateIndex,list):
604            self.handle_nextPageTemplate(self._firstPageTemplateIndex)
605            self._setPageTemplate()
606        else:
607            self.pageTemplate = self.pageTemplates[self._firstPageTemplateIndex]
608        self.page = 0
609        self.beforeDocument()
610
611    def handle_pageBegin(self):
612        """Perform actions required at beginning of page.
613        shouldn't normally be called directly"""
614        self.page += 1
615        if self._debug: logger.debug("beginning page %d" % self.page)
616        self.pageTemplate.beforeDrawPage(self.canv,self)
617        self.pageTemplate.checkPageSize(self.canv,self)
618        self.pageTemplate.onPage(self.canv,self)
619        for f in self.pageTemplate.frames: f._reset()
620        self.beforePage()
621        #keep a count of flowables added to this page.  zero indicates bad stuff
622        self._curPageFlowableCount = 0
623        if hasattr(self,'_nextFrameIndex'):
624            del self._nextFrameIndex
625        self.frame = self.pageTemplate.frames[0]
626        self.frame._debug = self._debug
627        self.handle_frameBegin(pageTopFlowables=self._pageTopFlowables)
628
629    def _setPageTemplate(self):
630        if hasattr(self,'_nextPageTemplateCycle'):
631            #they are cycling through pages'; we keep the index
632            self.pageTemplate = self._nextPageTemplateCycle.next_value
633        elif hasattr(self,'_nextPageTemplateIndex'):
634            self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex]
635            del self._nextPageTemplateIndex
636        elif self.pageTemplate.autoNextPageTemplate:
637            self.handle_nextPageTemplate(self.pageTemplate.autoNextPageTemplate)
638            self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex]
639
640    def _samePT(self,npt):
641        if isSeq(npt):
642            return getattr(self,'_nextPageTemplateCycle',[])
643        if isinstance(npt,strTypes):
644            return npt == (self.pageTemplates[self._nextPageTemplateIndex].id if hasattr(self,'_nextPageTemplateIndex') else self.pageTemplate.id)
645        if isinstance(npt,int) and 0<=npt<len(self.pageTemplates):
646            if hasattr(self,'_nextPageTemplateIndex'):
647                return npt==self._nextPageTemplateIndex
648            return npt==self.pageTemplates.find(self.pageTemplate)
649
650    def handle_pageEnd(self):
651        ''' show the current page
652            check the next page template
653            hang a page begin
654        '''
655        self._removeVars(('page','frame'))
656        self._leftExtraIndent = self.frame._leftExtraIndent
657        self._rightExtraIndent = self.frame._rightExtraIndent
658        self._frameBGs = self.frame._frameBGs
659        #detect infinite loops...
660        if self._curPageFlowableCount == 0:
661            self._emptyPages += 1
662        else:
663            self._emptyPages = 0
664        if self._emptyPages >= self._emptyPagesAllowed:
665            if 1:
666                ident = "More than %d pages generated without content - halting layout.  Likely that a flowable is too large for any frame." % self._emptyPagesAllowed
667                #leave to keep apart from the raise
668                raise LayoutError(ident)
669            else:
670                pass    #attempt to restore to good state
671        else:
672            if self._onProgress:
673                self._onProgress('PAGE', self.canv.getPageNumber())
674            self.pageTemplate.afterDrawPage(self.canv, self)
675            self.pageTemplate.onPageEnd(self.canv, self)
676            self.afterPage()
677            if self._debug: logger.debug("ending page %d" % self.page)
678            self.canv.setPageRotation(getattr(self.pageTemplate,'rotation',self.rotation))
679            self.canv.showPage()
680            self._setPageTemplate()
681            if self._emptyPages==0:
682                pass    #store good state here
683        self._hanging.append(PageBegin)
684
685    def handle_pageBreak(self,slow=None):
686        '''some might choose not to end all the frames'''
687        if self._pageBreakQuick and not slow:
688            self.handle_pageEnd()
689        else:
690            n = len(self._hanging)
691            while len(self._hanging)==n:
692                self.handle_frameEnd()
693
694    def handle_frameBegin(self,resume=0,pageTopFlowables=None):
695        '''What to do at the beginning of a frame'''
696        f = self.frame
697        if f._atTop:
698            boundary = self.frame.showBoundary or self.showBoundary
699            if boundary:
700                self.frame.drawBoundary(self.canv,boundary)
701        f._leftExtraIndent = self._leftExtraIndent
702        f._rightExtraIndent = self._rightExtraIndent
703        f._frameBGs = self._frameBGs
704        if pageTopFlowables:
705            self._hanging.extend(pageTopFlowables)
706        if self._topFlowables:
707            self._hanging.extend(self._topFlowables)
708
709    def handle_frameEnd(self,resume=0):
710        ''' Handles the semantics of the end of a frame. This includes the selection of
711            the next frame or if this is the last frame then invoke pageEnd.
712        '''
713        self._removeVars(('frame',))
714        self._leftExtraIndent = self.frame._leftExtraIndent
715        self._rightExtraIndent = self.frame._rightExtraIndent
716        self._frameBGs = self.frame._frameBGs
717
718        if hasattr(self,'_nextFrameIndex'):
719            self.frame = self.pageTemplate.frames[self._nextFrameIndex]
720            self.frame._debug = self._debug
721            del self._nextFrameIndex
722            self.handle_frameBegin(resume)
723        else:
724            f = self.frame
725            if hasattr(f,'lastFrame') or f is self.pageTemplate.frames[-1]:
726                self.handle_pageEnd()
727                self.frame = None
728            else:
729                self.frame = self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1]
730                self.frame._debug = self._debug
731                self.handle_frameBegin()
732
733    def handle_nextPageTemplate(self,pt):
734        '''On endPage change to the page template with name or index pt'''
735        if isinstance(pt,strTypes):
736            if hasattr(self, '_nextPageTemplateCycle'): del self._nextPageTemplateCycle
737            for t in self.pageTemplates:
738                if t.id == pt:
739                    self._nextPageTemplateIndex = self.pageTemplates.index(t)
740                    return
741            raise ValueError("can't find template('%s')"%pt)
742        elif isinstance(pt,int):
743            if hasattr(self, '_nextPageTemplateCycle'): del self._nextPageTemplateCycle
744            self._nextPageTemplateIndex = pt
745        elif isSeq(pt):
746            #used for alternating left/right pages
747            #collect the refs to the template objects, complain if any are bad
748            c = PTCycle()
749            for ptn in pt:
750                found = 0
751                if ptn=='*':    #special case name used to short circuit the iteration
752                    c._restart = len(c)
753                    continue
754                for t in self.pageTemplates:
755                    if t.id == ptn:
756                        c.append(t)
757                        found = 1
758                if not found:
759                    raise ValueError("Cannot find page template called %s" % ptn)
760            if not c:
761                raise ValueError("No valid page templates in cycle")
762            elif c._restart>len(c):
763                raise ValueError("Invalid cycle restart position")
764
765            #ensure we start on the first one
766            self._nextPageTemplateCycle = c
767        else:
768            raise TypeError("argument pt should be string or integer or list")
769
770    def _peekNextPageTemplate(self,pt):
771        if isinstance(pt,strTypes):
772            for t in self.pageTemplates:
773                if t.id == pt:
774                    return t
775            raise ValueError("can't find template('%s')"%pt)
776        elif isinstance(pt,int):
777            self.pageTemplates[pt]
778        elif isSeq(pt):
779            #used for alternating left/right pages
780            #collect the refs to the template objects, complain if any are bad
781            c = PTCycle()
782            for ptn in pt:
783                found = 0
784                if ptn=='*':    #special case name used to short circuit the iteration
785                    c._restart = len(c)
786                    continue
787                for t in self.pageTemplates:
788                    if t.id == ptn:
789                        c.append(t)
790                        found = 1
791                if not found:
792                    raise ValueError("Cannot find page template called %s" % ptn)
793            if not c:
794                raise ValueError("No valid page templates in cycle")
795            elif c._restart>len(c):
796                raise ValueError("Invalid cycle restart position")
797            return c.peek
798        else:
799            raise TypeError("argument pt should be string or integer or list")
800
801    def _peekNextFrame(self):
802        '''intended to be used by extreme flowables'''
803        if hasattr(self,'_nextFrameIndex'):
804            return self.pageTemplate.frames[self._nextFrameIndex]
805        f = self.frame
806        if hasattr(f,'lastFrame') or f is self.pageTemplate.frames[-1]:
807            if hasattr(self,'_nextPageTemplateCycle'):
808                #they are cycling through pages'; we keep the index
809                pageTemplate = self._nextPageTemplateCycle.peek
810            elif hasattr(self,'_nextPageTemplateIndex'):
811                pageTemplate = self.pageTemplates[self._nextPageTemplateIndex]
812            elif self.pageTemplate.autoNextPageTemplate:
813                pageTemplate = self._peekNextPageTemplate(self.pageTemplate.autoNextPageTemplate)
814            else:
815                pageTemplate = self.pageTemplate
816            return pageTemplate.frames[0]
817        else:
818            return self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1]
819
820    def handle_nextFrame(self,fx,resume=0):
821        '''On endFrame change to the frame with name or index fx'''
822        if isinstance(fx,strTypes):
823            for f in self.pageTemplate.frames:
824                if f.id == fx:
825                    self._nextFrameIndex = self.pageTemplate.frames.index(f)
826                    return
827            raise ValueError("can't find frame('%s') in %r(%s) which has frames %r"%(fx,self.pageTemplate,self.pageTemplate.id,[(f,f.id) for f in self.pageTemplate.frames]))
828        elif isinstance(fx,int):
829            self._nextFrameIndex = fx
830        else:
831            raise TypeError("argument fx should be string or integer")
832
833    def handle_currentFrame(self,fx,resume=0):
834        '''change to the frame with name or index fx'''
835        self.handle_nextFrame(fx,resume)
836        self.handle_frameEnd(resume)
837
838    def handle_breakBefore(self, flowables):
839        '''preprocessing step to allow pageBreakBefore and frameBreakBefore attributes'''
840        first = flowables[0]
841        # if we insert a page break before, we'll process that, see it again,
842        # and go in an infinite loop.  So we need to set a flag on the object
843        # saying 'skip me'.  This should be unset on the next pass
844        if hasattr(first, '_skipMeNextTime'):
845            delattr(first, '_skipMeNextTime')
846            return
847        # this could all be made much quicker by putting the attributes
848        # in to the flowables with a defult value of 0
849        if hasattr(first,'pageBreakBefore') and first.pageBreakBefore == 1:
850            first._skipMeNextTime = 1
851            first.insert(0, PageBreak())
852            return
853        if hasattr(first,'style') and hasattr(first.style, 'pageBreakBefore') and first.style.pageBreakBefore == 1:
854            first._skipMeNextTime = 1
855            flowables.insert(0, PageBreak())
856            return
857        if hasattr(first,'frameBreakBefore') and first.frameBreakBefore == 1:
858            first._skipMeNextTime = 1
859            flowables.insert(0, FrameBreak())
860            return
861        if hasattr(first,'style') and hasattr(first.style, 'frameBreakBefore') and first.style.frameBreakBefore == 1:
862            first._skipMeNextTime = 1
863            flowables.insert(0, FrameBreak())
864            return
865
866    def handle_keepWithNext(self, flowables):
867        "implements keepWithNext"
868        i = 0
869        n = len(flowables)
870        while i<n and flowables[i].getKeepWithNext() and _ktAllow(flowables[i]): i += 1
871        if i:
872            if i<n and _ktAllow(flowables[i]): i += 1
873            K = self.keepTogetherClass(flowables[:i])
874            mbe = getattr(self,'_multiBuildEdits',None)
875            if mbe:
876                for f in K._content[:-1]:
877                    if hasattr(f,'keepWithNext'):
878                        mbe((setattr,f,'keepWithNext',f.keepWithNext))
879                    else:
880                        mbe((delattr,f,'keepWithNext')) #must get it from a style
881                    f.__dict__['keepWithNext'] = 0
882            else:
883                for f in K._content[:-1]:
884                    f.__dict__['keepWithNext'] = 0
885            del flowables[:i]
886            flowables.insert(0,K)
887
888    def _fIdent(self,f,maxLen=None,frame=None):
889        if frame: f._frame = frame
890        try:
891            return f.identity(maxLen)
892        finally:
893            if frame: del f._frame
894
895    def handle_flowable(self,flowables):
896        '''try to handle one flowable from the front of list flowables.'''
897
898        #allow document a chance to look at, modify or ignore
899        #the object(s) about to be processed
900        self.filterFlowables(flowables)
901
902        f = flowables[0]
903        if f:
904            self.handle_breakBefore(flowables)
905            self.handle_keepWithNext(flowables)
906            f = flowables[0]
907        del flowables[0]
908        if f is None:
909            return
910
911        if isinstance(f,PageBreak):
912            npt = f.nextTemplate
913            if npt and not self._samePT(npt):
914                npt=NextPageTemplate(npt)
915                npt.apply(self)
916                self.afterFlowable(npt)
917            if isinstance(f,SlowPageBreak):
918                self.handle_pageBreak(slow=1)
919            else:
920                self.handle_pageBreak()
921            self.afterFlowable(f)
922        elif isinstance(f,ActionFlowable):
923            f.apply(self)
924            self.afterFlowable(f)
925        else:
926            frame = self.frame
927            canv = self.canv
928            #try to fit it then draw it
929            if frame.add(f, canv, trySplit=self.allowSplitting):
930                if not isinstance(f,FrameActionFlowable):
931                    self._curPageFlowableCount += 1
932                    self.afterFlowable(f)
933                _addGeneratedContent(flowables,frame)
934            else:
935                if self.allowSplitting:
936                    # see if this is a splittable thing
937                    S = frame.split(f,canv)
938                    n = len(S)
939                else:
940                    n = 0
941                if n:
942                    if not isinstance(S[0],(PageBreak,SlowPageBreak,ActionFlowable,DDIndenter)):
943                        if not frame.add(S[0], canv, trySplit=0):
944                            ident = "Splitting error(n==%d) on page %d in\n%s\nS[0]=%s" % (n,self.page,self._fIdent(f,60,frame),self._fIdent(S[0],60,frame))
945                            #leave to keep apart from the raise
946                            raise LayoutError(ident)
947                        self._curPageFlowableCount += 1
948                        self.afterFlowable(S[0])
949                        flowables[0:0] = S[1:]  # put rest of splitted flowables back on the list
950                        _addGeneratedContent(flowables,frame)
951                    else:
952                        flowables[0:0] = S  # put split flowables back on the list
953                else:
954                    if hasattr(f,'_postponed'):
955                        ident = "Flowable %s%s too large on page %d in frame %r%s of template %r" % \
956                                (self._fIdent(f,60,frame),_fSizeString(f),self.page, self.frame.id,
957                                        self.frame._aSpaceString(), self.pageTemplate.id)
958                        #leave to keep apart from the raise
959                        raise LayoutError(ident)
960                    # this ought to be cleared when they are finally drawn!
961                    f._postponed = 1
962                    mbe = getattr(self,'_multiBuildEdits',None)
963                    if mbe:
964                        mbe((delattr,f,'_postponed'))
965                    flowables.insert(0,f)           # put the flowable back
966                    self.handle_frameEnd()
967
968    #these are provided so that deriving classes can refer to them
969    _handle_documentBegin = handle_documentBegin
970    _handle_pageBegin = handle_pageBegin
971    _handle_pageEnd = handle_pageEnd
972    _handle_frameBegin = handle_frameBegin
973    _handle_frameEnd = handle_frameEnd
974    _handle_flowable = handle_flowable
975    _handle_nextPageTemplate = handle_nextPageTemplate
976    _handle_currentFrame = handle_currentFrame
977    _handle_nextFrame = handle_nextFrame
978
979    def _makeCanvas(self, filename=None, canvasmaker=canvas.Canvas):
980        '''make and return a sample canvas. As suggested by
981        Chris Jerdonek cjerdonek @ bitbucket this allows testing of stringWidths
982        etc.
983
984        *NB* only the canvases created in self._startBuild will actually be used
985        in the build process.
986        '''
987        #each distinct pass gets a sequencer
988        self.seq = reportlab.lib.sequencer.Sequencer()
989        canv = canvasmaker(filename or self.filename,
990                            pagesize=self.pagesize,
991                            invariant=self.invariant,
992                            pageCompression=self.pageCompression,
993                            enforceColorSpace=self.enforceColorSpace,
994                            initialFontName = self.initialFontName,
995                            initialFontSize = self.initialFontSize,
996                            initialLeading = self.initialLeading,
997                            cropBox = self.cropBox,
998                            artBox = self.artBox,
999                            trimBox = self.trimBox,
1000                            bleedBox = self.bleedBox,
1001                            lang = self.lang,
1002                            )
1003
1004        getattr(canv,'setEncrypt',lambda x: None)(self.encrypt)
1005
1006        canv._cropMarks = self.cropMarks
1007        canv.setAuthor(self.author)
1008        canv.setTitle(self.title)
1009        canv.setSubject(self.subject)
1010        canv.setCreator(self.creator)
1011        canv.setProducer(self.producer)
1012        canv.setKeywords(self.keywords)
1013        from reportlab.pdfbase.pdfdoc import (
1014                ViewerPreferencesPDFDictionary as VPD, checkPDFBoolean as cPDFB,
1015                )
1016        for k,vf in VPD.validate.items():
1017            v = getattr(self,k[0].lower()+k[1:],None)
1018            if v is not None:
1019                if vf is cPDFB:
1020                    v = ['false','true'][v] #convert to pdf form of boolean
1021                canv.setViewerPreference(k,v)
1022
1023        if self._onPage:
1024            canv.setPageCallBack(self._onPage)
1025        return canv
1026
1027    def _startBuild(self, filename=None, canvasmaker=canvas.Canvas):
1028        self._calc()
1029        self.canv = self._makeCanvas(filename=filename,canvasmaker=canvasmaker)
1030        self.handle_documentBegin()
1031
1032    def _endBuild(self):
1033        self._removeVars(('build','page','frame'))
1034        if self._hanging!=[] and self._hanging[-1] is PageBegin:
1035            del self._hanging[-1]
1036            self.clean_hanging()
1037        else:
1038            self.clean_hanging()
1039            self.handle_pageBreak()
1040
1041        if getattr(self,'_doSave',1): self.canv.save()
1042        if self._onPage: self.canv.setPageCallBack(None)
1043
1044    def build(self, flowables, filename=None, canvasmaker=canvas.Canvas):
1045        """Build the document from a list of flowables.
1046           If the filename argument is provided then that filename is used
1047           rather than the one provided upon initialization.
1048           If the canvasmaker argument is provided then it will be used
1049           instead of the default.  For example a slideshow might use
1050           an alternate canvas which places 6 slides on a page (by
1051           doing translations, scalings and redefining the page break
1052           operations).
1053        """
1054        #assert filter(lambda x: not isinstance(x,Flowable), flowables)==[], "flowables argument error"
1055        flowableCount = len(flowables)
1056        if self._onProgress:
1057            self._onProgress('STARTED',0)
1058            self._onProgress('SIZE_EST', len(flowables))
1059        self._startBuild(filename,canvasmaker)
1060
1061        #pagecatcher can drag in information from embedded PDFs and we want ours
1062        #to take priority, so cache and reapply our own info dictionary after the build.
1063        canv = self.canv
1064        self._savedInfo = canv._doc.info
1065        handled = 0
1066
1067        try:
1068            canv._doctemplate = self
1069            while len(flowables):
1070                if self._hanging and self._hanging[-1] is PageBegin and isinstance(flowables[0],PageBreakIfNotEmpty):
1071                    npt = flowables[0].nextTemplate
1072                    if npt and not self._samePT(npt):
1073                        npt=NextPageTemplate(npt)
1074                        npt.apply(self)
1075                        self._setPageTemplate()
1076                    del flowables[0]
1077                self.clean_hanging()
1078                try:
1079                    first = flowables[0]
1080                    self.handle_flowable(flowables)
1081                    handled += 1
1082                except:
1083                    #if it has trace info, add it to the traceback message.
1084                    if hasattr(first, '_traceInfo') and first._traceInfo:
1085                        exc = sys.exc_info()[1]
1086                        args = list(exc.args)
1087                        tr = first._traceInfo
1088                        args[0] += '\n(srcFile %s, line %d char %d to line %d char %d)' % (
1089                            tr.srcFile,
1090                            tr.startLineNo,
1091                            tr.startLinePos,
1092                            tr.endLineNo,
1093                            tr.endLinePos
1094                            )
1095                        exc.args = tuple(args)
1096                    raise
1097                if self._onProgress:
1098                    self._onProgress('PROGRESS',flowableCount - len(flowables))
1099        finally:
1100            del canv._doctemplate
1101
1102
1103        #reapply pagecatcher info
1104        canv._doc.info = self._savedInfo
1105
1106        self._endBuild()
1107        if self._onProgress:
1108            self._onProgress('FINISHED',0)
1109
1110    def _allSatisfied(self):
1111        """Called by multi-build - are all cross-references resolved?"""
1112        allHappy = 1
1113        for f in self._indexingFlowables:
1114            if not f.isSatisfied():
1115                allHappy = 0
1116                break
1117        return allHappy
1118
1119    def notify(self, kind, stuff):
1120        """Forward to any listeners"""
1121        for l in self._indexingFlowables:
1122            _canv = getattr(l,'_canv',self)
1123            try:
1124                if _canv==self:
1125                    l._canv = self.canv
1126                l.notify(kind, stuff)
1127            finally:
1128                if _canv==self:
1129                    del l._canv
1130
1131    def pageRef(self, label):
1132        """hook to register a page number"""
1133        if verbose: print("pageRef called with label '%s' on page %d" % (
1134            label, self.page))
1135        self._pageRefs[label] = self.page
1136
1137    def multiBuild(self, story,
1138                   maxPasses = 10,
1139                   **buildKwds
1140                   ):
1141        """Makes multiple passes until all indexing flowables
1142        are happy.
1143
1144        Returns number of passes"""
1145        self._indexingFlowables = []
1146        #scan the story and keep a copy
1147        for thing in story:
1148            if thing.isIndexing():
1149                self._indexingFlowables.append(thing)
1150
1151        #better fix for filename is a 'file' problem
1152        self._doSave = 0
1153        passes = 0
1154        mbe = []
1155        self._multiBuildEdits = mbe.append
1156        while 1:
1157            passes += 1
1158            if self._onProgress:
1159                self._onProgress('PASS', passes)
1160            if verbose: sys.stdout.write('building pass '+str(passes) + '...')
1161
1162            for fl in self._indexingFlowables:
1163                fl.beforeBuild()
1164
1165            # work with a copy of the story, since it is consumed
1166            tempStory = story[:]
1167            self.build(tempStory, **buildKwds)
1168            #self.notify('debug',None)
1169
1170            for fl in self._indexingFlowables:
1171                fl.afterBuild()
1172
1173            happy = self._allSatisfied()
1174
1175            if happy:
1176                self._doSave = 0
1177                self.canv.save()
1178                break
1179            if passes > maxPasses:
1180                raise IndexError("Index entries not resolved after %d passes" % maxPasses)
1181
1182            #work through any edits
1183            while mbe:
1184                e = mbe.pop(0)
1185                e[0](*e[1:])
1186
1187        del self._multiBuildEdits
1188        if verbose: print('saved')
1189        return passes
1190
1191    #these are pure virtuals override in derived classes
1192    #NB these get called at suitable places by the base class
1193    #so if you derive and override the handle_xxx methods
1194    #it's up to you to ensure that they maintain the needed consistency
1195    def afterInit(self):
1196        """This is called after initialisation of the base class."""
1197        pass
1198
1199    def beforeDocument(self):
1200        """This is called before any processing is
1201        done on the document."""
1202        pass
1203
1204    def beforePage(self):
1205        """This is called at the beginning of page
1206        processing, and immediately before the
1207        beforeDrawPage method of the current page
1208        template."""
1209        pass
1210
1211    def afterPage(self):
1212        """This is called after page processing, and
1213        immediately after the afterDrawPage method
1214        of the current page template."""
1215        pass
1216
1217    def filterFlowables(self,flowables):
1218        '''called to filter flowables at the start of the main handle_flowable method.
1219        Upon return if flowables[0] has been set to None it is discarded and the main
1220        method returns.
1221        '''
1222        pass
1223
1224    def afterFlowable(self, flowable):
1225        '''called after a flowable has been rendered'''
1226        pass
1227
1228    _allowedLifetimes = 'page','frame','build','forever'
1229    def docAssign(self,var,expr,lifetime):
1230        if not isinstance(expr,strTypes): expr=str(expr)
1231        expr=expr.strip()
1232        var=var.strip()
1233        self.docExec('%s=(%s)'%(var.strip(),expr.strip()),lifetime)
1234
1235    def docExec(self,stmt,lifetime):
1236        stmt=stmt.strip()
1237        NS=self._nameSpace
1238        K0=list(NS.keys())
1239        try:
1240            if lifetime not in self._allowedLifetimes:
1241                raise ValueError('bad lifetime %r not in %r'%(lifetime,self._allowedLifetimes))
1242            exec(stmt, NS)
1243        except:
1244            K1 = [k for k in NS if k not in K0] #the added keys we need to delete
1245            for k in K1:
1246                del NS[k]
1247            annotateException('\ndocExec %s lifetime=%r failed!\n' % (stmt,lifetime))
1248        self._addVars([k for k in NS.keys() if k not in K0],lifetime)
1249
1250    def _addVars(self,vars,lifetime):
1251        '''add namespace variables to lifetimes lists'''
1252        LT=self._lifetimes
1253        for var in vars:
1254            for v in LT.values():
1255                if var in v:
1256                    v.remove(var)
1257            LT.setdefault(lifetime,set([])).add(var)
1258
1259    def _removeVars(self,lifetimes):
1260        '''remove namespace variables for with lifetime in lifetimes'''
1261        LT=self._lifetimes
1262        NS=self._nameSpace
1263        for lifetime in lifetimes:
1264            for k in LT.setdefault(lifetime,[]):
1265                try:
1266                    del NS[k]
1267                except KeyError:
1268                    pass
1269            del LT[lifetime]
1270
1271    def docEval(self,expr):
1272        try:
1273            return eval(expr.strip(),{},self._nameSpace)
1274        except:
1275            annotateException('\ndocEval %s failed!\n' % expr)
1276
1277class SimpleDocTemplate(BaseDocTemplate):
1278    """A special case document template that will handle many simple documents.
1279       See documentation for BaseDocTemplate.  No pageTemplates are required
1280       for this special case.   A page templates are inferred from the
1281       margin information and the onFirstPage, onLaterPages arguments to the build method.
1282
1283       A document which has all pages with the same look except for the first
1284       page may can be built using this special approach.
1285    """
1286    _invalidInitArgs = ('pageTemplates',)
1287
1288    def handle_pageBegin(self):
1289        '''override base method to add a change of page template after the firstpage.
1290        '''
1291        self._handle_pageBegin()
1292        self._handle_nextPageTemplate('Later')
1293
1294    def build(self,flowables,onFirstPage=_doNothing, onLaterPages=_doNothing, canvasmaker=canvas.Canvas):
1295        """build the document using the flowables.  Annotate the first page using the onFirstPage
1296               function and later pages using the onLaterPages function.  The onXXX pages should follow
1297               the signature
1298
1299                  def myOnFirstPage(canvas, document):
1300                      # do annotations and modify the document
1301                      ...
1302
1303               The functions can do things like draw logos, page numbers,
1304               footers, etcetera. They can use external variables to vary
1305               the look (for example providing page numbering or section names).
1306        """
1307        self._calc()    #in case we changed margins sizes etc
1308        frameT = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='normal')
1309        self.addPageTemplates([PageTemplate(id='First',frames=frameT, onPage=onFirstPage,pagesize=self.pagesize),
1310                        PageTemplate(id='Later',frames=frameT, onPage=onLaterPages,pagesize=self.pagesize)])
1311        if onFirstPage is _doNothing and hasattr(self,'onFirstPage'):
1312            self.pageTemplates[0].beforeDrawPage = self.onFirstPage
1313        if onLaterPages is _doNothing and hasattr(self,'onLaterPages'):
1314            self.pageTemplates[1].beforeDrawPage = self.onLaterPages
1315        BaseDocTemplate.build(self,flowables, canvasmaker=canvasmaker)
1316
1317def progressCB(typ, value):
1318    """Example prototype for progress monitoring.
1319
1320    This aims to provide info about what is going on
1321    during a big job.  It should enable, for example, a reasonably
1322    smooth progress bar to be drawn.  We design the argument
1323    signature to be predictable and conducive to programming in
1324    other (type safe) languages.  If set, this will be called
1325    repeatedly with pairs of values.  The first is a string
1326    indicating the type of call; the second is a numeric value.
1327
1328    typ 'STARTING', value = 0
1329    typ 'SIZE_EST', value = numeric estimate of job size
1330    typ 'PASS', value = number of this rendering pass
1331    typ 'PROGRESS', value = number between 0 and SIZE_EST
1332    typ 'PAGE', value = page number of page
1333    type 'FINISHED', value = 0
1334
1335    The sequence is
1336        STARTING - always called once
1337        SIZE_EST - always called once
1338        PROGRESS - called often
1339        PAGE - called often when page is emitted
1340        FINISHED - called when really, really finished
1341
1342    some juggling is needed to accurately estimate numbers of
1343    pages in pageDrawing mode.
1344
1345    NOTE: the SIZE_EST is a guess.  It is possible that the
1346    PROGRESS value may slightly exceed it, or may even step
1347    back a little on rare occasions.  The only way to be
1348    really accurate would be to do two passes, and I don't
1349    want to take that performance hit.
1350    """
1351    print('PROGRESS MONITOR:  %-10s   %d' % (typ, value))
1352
1353if __name__ == '__main__':
1354    from reportlab.lib.styles import _baseFontName, _baseFontNameB
1355    def myFirstPage(canvas, doc):
1356        from reportlab.lib.colors import red
1357        PAGE_HEIGHT = canvas._pagesize[1]
1358        canvas.saveState()
1359        canvas.setStrokeColor(red)
1360        canvas.setLineWidth(5)
1361        canvas.line(66,72,66,PAGE_HEIGHT-72)
1362        canvas.setFont(_baseFontNameB,24)
1363        canvas.drawString(108, PAGE_HEIGHT-108, "TABLE OF CONTENTS DEMO")
1364        canvas.setFont(_baseFontName,12)
1365        canvas.drawString(4 * inch, 0.75 * inch, "First Page")
1366        canvas.restoreState()
1367
1368    def myLaterPages(canvas, doc):
1369        from reportlab.lib.colors import red
1370        PAGE_HEIGHT = canvas._pagesize[1]
1371        canvas.saveState()
1372        canvas.setStrokeColor(red)
1373        canvas.setLineWidth(5)
1374        canvas.line(66,72,66,PAGE_HEIGHT-72)
1375        canvas.setFont(_baseFontName,12)
1376        canvas.drawString(4 * inch, 0.75 * inch, "Page %d" % doc.page)
1377        canvas.restoreState()
1378
1379    def run():
1380        objects_to_draw = []
1381        from reportlab.lib.styles import ParagraphStyle
1382        #from paragraph import Paragraph
1383        from reportlab.platypus.doctemplate import SimpleDocTemplate
1384
1385        #need a style
1386        normal = ParagraphStyle('normal')
1387        normal.firstLineIndent = 18
1388        normal.spaceBefore = 6
1389        from reportlab.lib.randomtext import randomText
1390        import random
1391        for i in range(15):
1392            height = 0.5 + (2*random.random())
1393            box = XBox(6 * inch, height * inch, 'Box Number %d' % i)
1394            objects_to_draw.append(box)
1395            para = Paragraph(randomText(), normal)
1396            objects_to_draw.append(para)
1397
1398        SimpleDocTemplate('doctemplate.pdf').build(objects_to_draw,
1399            onFirstPage=myFirstPage,onLaterPages=myLaterPages)
1400
1401    run()
1402