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/flowables.py
4__version__='3.3.0'
5__doc__="""
6A flowable is a "floating element" in a document whose exact position is determined by the
7other elements that precede it, such as a paragraph, a diagram interspersed between paragraphs,
8a section header, etcetera.  Examples of non-flowables include page numbering annotations,
9headers, footers, fixed diagrams or logos, among others.
10
11Flowables are defined here as objects which know how to determine their size and which
12can draw themselves onto a page with respect to a relative "origin" position determined
13at a higher level. The object's draw() method should assume that (0,0) corresponds to the
14bottom left corner of the enclosing rectangle that will contain the object. The attributes
15vAlign and hAlign may be used by 'packers' as hints as to how the object should be placed.
16
17Some Flowables also know how to "split themselves".  For example a
18long paragraph might split itself between one page and the next.
19
20Packers should set the canv attribute during wrap, split & draw operations to allow
21the flowable to work out sizes etc in the proper context.
22
23The "text" of a document usually consists mainly of a sequence of flowables which
24flow into a document from top to bottom (with column and page breaks controlled by
25higher level components).
26"""
27import os
28from copy import deepcopy, copy
29from reportlab.lib.colors import red, gray, lightgrey
30from reportlab.lib.rl_accel import fp_str
31from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
32from reportlab.lib.styles import _baseFontName
33from reportlab.lib.utils import strTypes, rl_safe_exec
34from reportlab.lib.abag import ABag
35from reportlab.pdfbase import pdfutils
36from reportlab.pdfbase.pdfmetrics import stringWidth
37from reportlab.rl_config import _FUZZ, overlapAttachedSpace, ignoreContainerActions, listWrapOnFakeWidth
38from reportlab import xrange
39import collections
40
41__all__ = '''AnchorFlowable BalancedColumns BulletDrawer CallerMacro CondPageBreak DDIndenter DocAssert
42        DocAssign DocExec DocIf DocPara DocWhile FailOnDraw FailOnWrap Flowable FrameBG FrameSplitter
43        HRFlowable Image ImageAndFlowables KeepInFrame KeepTogether LIIndenter ListFlowable ListItem
44        Macro NullDraw PTOContainer PageBreak PageBreakIfNotEmpty ParagraphAndImage Preformatted
45        SetPageTopFlowables SetTopFlowables SlowPageBreak Spacer TopPadder TraceInfo UseUpSpace XBox
46        splitLine splitLines'''.split()
47
48class TraceInfo:
49    "Holder for info about where an object originated"
50    def __init__(self):
51        self.srcFile = '(unknown)'
52        self.startLineNo = -1
53        self.startLinePos = -1
54        self.endLineNo = -1
55        self.endLinePos = -1
56
57#############################################################
58#   Flowable Objects - a base class and a few examples.
59#   One is just a box to get some metrics.  We also have
60#   a paragraph, an image and a special 'page break'
61#   object which fills the space.
62#############################################################
63class Flowable:
64    """Abstract base class for things to be drawn.  Key concepts:
65
66    1. It knows its size
67    2. It draws in its own coordinate system (this requires the
68       base API to provide a translate() function.
69
70    """
71    _fixedWidth = 0         #assume wrap results depend on arguments?
72    _fixedHeight = 0
73
74    def __init__(self):
75        self.width = 0
76        self.height = 0
77        self.wrapped = 0
78
79        #these are hints to packers/frames as to how the floable should be positioned
80        self.hAlign = 'LEFT'    #CENTER/CENTRE or RIGHT
81        self.vAlign = 'BOTTOM'  #MIDDLE or TOP
82
83        #optional holder for trace info
84        self._traceInfo = None
85        self._showBoundary = None
86
87        #many flowables handle text and must be processed in the
88        #absence of a canvas.  tagging them with their encoding
89        #helps us to get conversions right.  Use Python codec names.
90        self.encoding = None
91
92    def _drawOn(self,canv):
93        '''ensure canv is set on and then draw'''
94        self.canv = canv
95        self.draw()#this is the bit you overload
96        del self.canv
97
98    def _hAlignAdjust(self,x,sW=0):
99        if sW and hasattr(self,'hAlign'):
100            a = self.hAlign
101            if a in ('CENTER','CENTRE', TA_CENTER):
102                x += 0.5*sW
103            elif a in ('RIGHT',TA_RIGHT):
104                x += sW
105            elif a not in ('LEFT',TA_LEFT):
106                raise ValueError("Bad hAlign value "+str(a))
107        return x
108
109    def drawOn(self, canvas, x, y, _sW=0):
110        "Tell it to draw itself on the canvas.  Do not override"
111        x = self._hAlignAdjust(x,_sW)
112        canvas.saveState()
113        canvas.translate(x, y)
114        self._drawOn(canvas)
115        if hasattr(self, '_showBoundary') and self._showBoundary:
116            #diagnostic tool support
117            canvas.setStrokeColor(gray)
118            canvas.rect(0,0,self.width, self.height)
119        canvas.restoreState()
120
121    def wrapOn(self, canv, aW, aH):
122        '''intended for use by packers allows setting the canvas on
123        during the actual wrap'''
124        self.canv = canv
125        w, h = self.wrap(aW,aH)
126        del self.canv
127        return w, h
128
129    def wrap(self, availWidth, availHeight):
130        """This will be called by the enclosing frame before objects
131        are asked their size, drawn or whatever.  It returns the
132        size actually used."""
133        return (self.width, self.height)
134
135    def minWidth(self):
136        """This should return the minimum required width"""
137        return getattr(self,'_minWidth',self.width)
138
139    def splitOn(self, canv, aW, aH):
140        '''intended for use by packers allows setting the canvas on
141        during the actual split'''
142        self.canv = canv
143        S = self.split(aW,aH)
144        del self.canv
145        return S
146
147    def split(self, availWidth, availheight):
148        """This will be called by more sophisticated frames when
149        wrap fails. Stupid flowables should return []. Clever flowables
150        should split themselves and return a list of flowables.
151        If they decide that nothing useful can be fitted in the
152        available space (e.g. if you have a table and not enough
153        space for the first row), also return []"""
154        return []
155
156    def getKeepWithNext(self):
157        """returns boolean determining whether the next flowable should stay with this one"""
158        if hasattr(self,'keepWithNext'): return self.keepWithNext
159        elif hasattr(self,'style') and hasattr(self.style,'keepWithNext'): return self.style.keepWithNext
160        else: return 0
161
162    def getSpaceAfter(self):
163        """returns how much space should follow this item if another item follows on the same page."""
164        if hasattr(self,'spaceAfter'): return self.spaceAfter
165        elif hasattr(self,'style') and hasattr(self.style,'spaceAfter'): return self.style.spaceAfter
166        else: return 0
167
168    def getSpaceBefore(self):
169        """returns how much space should precede this item if another item precedess on the same page."""
170        if hasattr(self,'spaceBefore'): return self.spaceBefore
171        elif hasattr(self,'style') and hasattr(self.style,'spaceBefore'): return self.style.spaceBefore
172        else: return 0
173
174    def isIndexing(self):
175        """Hook for IndexingFlowables - things which have cross references"""
176        return 0
177
178    def identity(self, maxLen=None):
179        '''
180        This method should attempt to return a string that can be used to identify
181        a particular flowable uniquely. The result can then be used for debugging
182        and or error printouts
183        '''
184        if hasattr(self, 'getPlainText'):
185            r = self.getPlainText(identify=1)
186        elif hasattr(self, 'text'):
187            r = str(self.text)
188        else:
189            r = '...'
190        if r and maxLen:
191            r = r[:maxLen]
192        return "<%s at %s%s>%s" % (self.__class__.__name__, hex(id(self)), self._frameName(), r)
193
194    @property
195    def _doctemplate(self):
196        return getattr(getattr(self,'canv',None),'_doctemplate',None)
197
198    def _doctemplateAttr(self,a):
199        return getattr(self._doctemplate,a,None)
200
201    def _frameName(self):
202        f = getattr(self,'_frame',None)
203        if not f: f = self._doctemplateAttr('frame')
204        if f and f.id: return ' frame=%s' % f.id
205        return ''
206
207class XBox(Flowable):
208    """Example flowable - a box with an x through it and a caption.
209    This has a known size, so does not need to respond to wrap()."""
210    def __init__(self, width, height, text = 'A Box'):
211        Flowable.__init__(self)
212        self.width = width
213        self.height = height
214        self.text = text
215
216    def __repr__(self):
217        return "XBox(w=%s, h=%s, t=%s)" % (self.width, self.height, self.text)
218
219    def draw(self):
220        self.canv.rect(0, 0, self.width, self.height)
221        self.canv.line(0, 0, self.width, self.height)
222        self.canv.line(0, self.height, self.width, 0)
223
224        #centre the text
225        self.canv.setFont(_baseFontName,12)
226        self.canv.drawCentredString(0.5*self.width, 0.5*self.height, self.text)
227
228def _trimEmptyLines(lines):
229    #don't want the first or last to be empty
230    while len(lines) and lines[0].strip() == '':
231        lines = lines[1:]
232    while len(lines) and lines[-1].strip() == '':
233        lines = lines[:-1]
234    return lines
235
236def _dedenter(text,dedent=0):
237    '''
238    tidy up text - carefully, it is probably code.  If people want to
239    indent code within a source script, you can supply an arg to dedent
240    and it will chop off that many character, otherwise it leaves
241    left edge intact.
242    '''
243    lines = text.split('\n')
244    if dedent>0:
245        templines = _trimEmptyLines(lines)
246        lines = []
247        for line in templines:
248            line = line[dedent:].rstrip()
249            lines.append(line)
250    else:
251        lines = _trimEmptyLines(lines)
252
253    return lines
254
255
256SPLIT_CHARS = "[{( ,.;:/\\-"
257
258def splitLines(lines, maximum_length, split_characters, new_line_characters):
259    if split_characters is None:
260        split_characters = SPLIT_CHARS
261    if new_line_characters is None:
262        new_line_characters = ""
263    # Return a table of lines
264    lines_splitted = []
265    for line in lines:
266        if len(line) > maximum_length:
267            splitLine(line, lines_splitted, maximum_length, \
268            split_characters, new_line_characters)
269        else:
270            lines_splitted.append(line)
271    return lines_splitted
272
273def splitLine(line_to_split, lines_splitted, maximum_length, \
274split_characters, new_line_characters):
275    # Used to implement the characters added
276    #at the beginning of each new line created
277    first_line = True
278
279    # Check if the text can be splitted
280    while line_to_split and len(line_to_split)>0:
281
282        # Index of the character where we can split
283        split_index = 0
284
285        # Check if the line length still exceeds the maximum length
286        if len(line_to_split) <= maximum_length:
287            # Return the remaining of the line
288            split_index = len(line_to_split)
289        else:
290            # Iterate for each character of the line
291            for line_index in range(maximum_length):
292                # Check if the character is in the list
293                # of allowed characters to split on
294                if line_to_split[line_index] in split_characters:
295                    split_index = line_index + 1
296
297        # If the end of the line was reached
298        # with no character to split on
299        if split_index==0:
300            split_index = line_index + 1
301
302        if first_line:
303            lines_splitted.append(line_to_split[0:split_index])
304            first_line = False
305            maximum_length -= len(new_line_characters)
306        else:
307            lines_splitted.append(new_line_characters + \
308            line_to_split[0:split_index])
309
310        # Remaining text to split
311        line_to_split = line_to_split[split_index:]
312
313class Preformatted(Flowable):
314    """This is like the HTML <PRE> tag.
315    It attempts to display text exactly as you typed it in a fixed width "typewriter" font.
316    By default the line breaks are exactly where you put them, and it will not be wrapped.
317    You can optionally define a maximum line length and the code will be wrapped; and
318    extra characters to be inserted at the beginning of each wrapped line (e.g. '> ').
319    """
320    def __init__(self, text, style, bulletText = None, dedent=0, maxLineLength=None, splitChars=None, newLineChars=""):
321        """text is the text to display. If dedent is set then common leading space
322        will be chopped off the front (for example if the entire text is indented
323        6 spaces or more then each line will have 6 spaces removed from the front).
324        """
325        self.style = style
326        self.bulletText = bulletText
327        self.lines = _dedenter(text,dedent)
328        if text and maxLineLength:
329            self.lines = splitLines(
330                                self.lines,
331                                maxLineLength,
332                                splitChars,
333                                newLineChars
334                        )
335
336    def __repr__(self):
337        bT = self.bulletText
338        H = "Preformatted("
339        if bT is not None:
340            H = "Preformatted(bulletText=%s," % repr(bT)
341        return "%s'''\\ \n%s''')" % (H, '\n'.join(self.lines))
342
343    def wrap(self, availWidth, availHeight):
344        self.width = availWidth
345        self.height = self.style.leading*len(self.lines)
346        return (self.width, self.height)
347
348    def minWidth(self):
349        style = self.style
350        fontSize = style.fontSize
351        fontName = style.fontName
352        return max([stringWidth(line,fontName,fontSize) for line in self.lines])
353
354    def split(self, availWidth, availHeight):
355        #returns two Preformatted objects
356
357        #not sure why they can be called with a negative height
358        if availHeight < self.style.leading:
359            return []
360
361        linesThatFit = int(availHeight * 1.0 / self.style.leading)
362
363        text1 = '\n'.join(self.lines[0:linesThatFit])
364        text2 = '\n'.join(self.lines[linesThatFit:])
365        style = self.style
366        if style.firstLineIndent != 0:
367            style = deepcopy(style)
368            style.firstLineIndent = 0
369        return [Preformatted(text1, self.style), Preformatted(text2, style)]
370
371    def draw(self):
372        #call another method for historical reasons.  Besides, I
373        #suspect I will be playing with alternate drawing routines
374        #so not doing it here makes it easier to switch.
375
376        cur_x = self.style.leftIndent
377        cur_y = self.height - self.style.fontSize
378        self.canv.addLiteral('%PreformattedPara')
379        if self.style.textColor:
380            self.canv.setFillColor(self.style.textColor)
381        tx = self.canv.beginText(cur_x, cur_y)
382        #set up the font etc.
383        tx.setFont( self.style.fontName,
384                    self.style.fontSize,
385                    self.style.leading)
386
387        for text in self.lines:
388            tx.textLine(text)
389        self.canv.drawText(tx)
390
391class Image(Flowable):
392    """an image (digital picture).  Formats supported by PIL/Java 1.4 (the Python/Java Imaging Library
393       are supported. Images as flowables may be aligned horizontally in the
394       frame with the hAlign parameter - accepted values are 'CENTER',
395       'LEFT' or 'RIGHT' with 'CENTER' being the default.
396       We allow for two kinds of lazyness to allow for many images in a document
397       which could lead to file handle starvation.
398       lazy=1 don't open image until required.
399       lazy=2 open image when required then shut it.
400    """
401    _fixedWidth = 1
402    _fixedHeight = 1
403    def __init__(self, filename, width=None, height=None, kind='direct',
404                 mask="auto", lazy=1, hAlign='CENTER', useDPI=False):
405        """If size to draw at not specified, get it from the image."""
406        self.hAlign = hAlign
407        self._mask = mask
408        fp = hasattr(filename,'read')
409        self._drawing = None
410        if fp:
411            self._file = filename
412            self.filename = repr(filename)
413        elif hasattr(filename,'_renderPy'):
414            self._drawing = filename
415            self.filename=repr(filename)
416            self._file = None
417            self._img = None
418            fp = True
419        else:
420            self._file = self.filename = filename
421        self._dpi = useDPI
422        if not fp and os.path.splitext(filename)[1] in ['.jpg', '.JPG', '.jpeg', '.JPEG']:
423            # if it is a JPEG, will be inlined within the file -
424            # but we still need to know its size now
425            from reportlab.lib.utils import open_for_read
426            f = open_for_read(filename, 'b')
427            try:
428                try:
429                    info = pdfutils.readJPEGInfo(f)
430                except:
431                    #couldn't read as a JPEG, try like normal
432                    self._setup(width,height,kind,lazy)
433                    return
434            finally:
435                f.close()
436            self.imageWidth = info[0]
437            self.imageHeight = info[1]
438            if useDPI:
439                self._dpi = info[3]
440            self._img = None
441            self._setup(width,height,kind,0)
442        elif fp:
443            self._setup(width,height,kind,0)
444        else:
445            self._setup(width,height,kind,lazy)
446
447    def _dpiAdjust(self):
448        dpi = self._dpi
449        if dpi:
450            if dpi[0]!=72: self.imageWidth *= 72.0 / dpi[0]
451            if dpi[1]!=72: self.imageHeight *= 72.0 / dpi[1]
452
453    def _setup(self,width,height,kind,lazy):
454        self._lazy = lazy
455        self._width = width
456        self._height = height
457        self._kind = kind
458        if lazy<=0: self._setup_inner()
459
460    def _setup_inner(self):
461        width = self._width
462        height = self._height
463        kind = self._kind
464        img = self._img
465        if img:
466            self.imageWidth, self.imageHeight = img.getSize()
467            if self._dpi and hasattr(img,'_image'):
468                self._dpi = img._image.info.get('dpi',(72,72))
469        elif self._drawing:
470            self.imageWidth, self.imageHeight = self._drawing.width,self._drawing.height
471            self._dpi = False
472        self._dpiAdjust()
473        if self._lazy>=2: del self._img
474        if kind in ['direct','absolute']:
475            self.drawWidth = width or self.imageWidth
476            self.drawHeight = height or self.imageHeight
477        elif kind in ['percentage','%']:
478            self.drawWidth = self.imageWidth*width*0.01
479            self.drawHeight = self.imageHeight*height*0.01
480        elif kind in ['bound','proportional']:
481            factor = min(float(width)/self.imageWidth,float(height)/self.imageHeight)
482            self.drawWidth = self.imageWidth*factor
483            self.drawHeight = self.imageHeight*factor
484
485    def _restrictSize(self,aW,aH):
486        if self.drawWidth>aW+_FUZZ or self.drawHeight>aH+_FUZZ:
487            self._oldDrawSize = self.drawWidth, self.drawHeight
488            factor = min(float(aW)/self.drawWidth,float(aH)/self.drawHeight)
489            self.drawWidth *= factor
490            self.drawHeight *= factor
491        return self.drawWidth, self.drawHeight
492
493    def _unRestrictSize(self):
494        dwh = getattr(self,'_oldDrawSize',None)
495        if dwh:
496            self.drawWidth, self.drawHeight = dwh
497
498    def __getattr__(self,a):
499        if a=='_img':
500            from reportlab.lib.utils import ImageReader  #this may raise an error
501            self._img = ImageReader(self._file)
502            if not isinstance(self._file,strTypes):
503                self._file = None
504                if self._lazy>=2: self._lazy = 1    #here we're assuming we cannot read again
505            return self._img
506        elif a in ('drawWidth','drawHeight','imageWidth','imageHeight'):
507            self._setup_inner()
508            return self.__dict__[a]
509        raise AttributeError("<Image @ 0x%x>.%s" % (id(self),a))
510
511    def wrap(self, availWidth, availHeight):
512        #the caller may decide it does not fit.
513        return self.drawWidth, self.drawHeight
514
515    def draw(self):
516        dx = getattr(self,'_offs_x',0)
517        dy = getattr(self,'_offs_y',0)
518        d = self._drawing
519        if d:
520            sx = self.drawWidth / float(self.imageWidth)
521            sy = self.drawHeight / float(self.imageHeight)
522            otrans = d.transform
523            try:
524                d.scale(sx,sy)
525                d.drawOn(self.canv,dx,dy)
526            finally:
527                d.transform = otrans
528        else:
529            lazy = self._lazy
530            if lazy>=2: self._lazy = 1
531            self.canv.drawImage(    self._img or self.filename,
532                                    dx,
533                                    dy,
534                                    self.drawWidth,
535                                    self.drawHeight,
536                                    mask=self._mask,
537                                    )
538            if lazy>=2:
539                self._img = self._file = None
540                self._lazy = lazy
541
542    def identity(self,maxLen=None):
543        r = Flowable.identity(self,maxLen)
544        if r[-4:]=='>...' and isinstance(self.filename,str):
545            r = "%s filename=%s>" % (r[:-4],self.filename)
546        return r
547
548class NullDraw(Flowable):
549    def draw(self):
550        pass
551
552class Spacer(NullDraw):
553    """A spacer just takes up space and doesn't draw anything - it guarantees
554       a gap between objects."""
555    _fixedWidth = 1
556    _fixedHeight = 1
557    def __init__(self, width, height, isGlue=False):
558        self.width = width
559        if isGlue:
560            self.height = 1e-4
561            self.spacebefore = height
562        self.height = height
563
564    def __repr__(self):
565        return "%s(%s, %s)" % (self.__class__.__name__,self.width, self.height)
566
567class UseUpSpace(NullDraw):
568    def __init__(self):
569        pass
570
571    def __repr__(self):
572        return "%s()" % self.__class__.__name__
573
574    def wrap(self, availWidth, availHeight):
575        self.width = availWidth
576        self.height = availHeight
577        return (availWidth,availHeight-1e-8)  #step back a point
578
579class PageBreak(UseUpSpace):
580    locChanger=1
581    """Move on to the next page in the document.
582       This works by consuming all remaining space in the frame!"""
583    def __init__(self,nextTemplate=None):
584        self.nextTemplate = nextTemplate
585
586    def __repr__(self):
587        return "%s(%s)" % (self.__class__.__name__,repr(self.nextTemplate) if self.nextTemplate else '')
588
589class SlowPageBreak(PageBreak):
590    pass
591
592class PageBreakIfNotEmpty(PageBreak):
593    pass
594
595class CondPageBreak(Spacer):
596    locChanger=1
597    """use up a frame if not enough vertical space effectively CondFrameBreak"""
598    def __init__(self, height):
599        self.height = height
600
601    def __repr__(self):
602        return "CondPageBreak(%s)" %(self.height,)
603
604    def wrap(self, availWidth, availHeight):
605        if availHeight<self.height:
606            f = self._doctemplateAttr('frame')
607            if not f: return availWidth, availHeight
608            from reportlab.platypus.doctemplate import FrameBreak
609            f.add_generated_content(FrameBreak)
610        return 0, 0
611
612    def identity(self,maxLen=None):
613        return repr(self).replace(')',',frame=%s)'%self._frameName())
614
615def _listWrapOn(F,availWidth,canv,mergeSpace=1,obj=None,dims=None,fakeWidth=None):
616    '''return max width, required height for a list of flowables F'''
617    doct = getattr(canv,'_doctemplate',None)
618    cframe = getattr(doct,'frame',None)
619    if fakeWidth is None:
620        fakeWidth = listWrapOnFakeWidth
621    if cframe:
622        from reportlab.platypus.doctemplate import _addGeneratedContent, Indenter
623        doct_frame = cframe
624        cframe = doct.frame = deepcopy(doct_frame)
625        cframe._generated_content = None
626        del cframe._generated_content
627    try:
628        W = 0
629        H = 0
630        pS = 0
631        atTop = 1
632        F = F[:]
633        while F:
634            f = F.pop(0)
635            if hasattr(f,'frameAction'):
636                from reportlab.platypus.doctemplate import Indenter
637                if isinstance(f,Indenter):
638                    availWidth -= f.left+f.right
639                continue
640            w,h = f.wrapOn(canv,availWidth,0xfffffff)
641            if dims is not None: dims.append((w,h))
642            if cframe:
643                _addGeneratedContent(F,cframe)
644            if w<=_FUZZ or h<=_FUZZ: continue
645            W = max(W,min(w,availWidth) if fakeWidth else w)
646            H += h
647            if not atTop:
648                h = f.getSpaceBefore()
649                if mergeSpace:
650                    if getattr(f,'_SPACETRANSFER',False):
651                        h = pS
652                    h = max(h-pS,0)
653                H += h
654            else:
655                if obj is not None: obj._spaceBefore = f.getSpaceBefore()
656                atTop = 0
657            s = f.getSpaceAfter()
658            if getattr(f,'_SPACETRANSFER',False):
659                s = pS
660            pS = s
661            H += pS
662        if obj is not None: obj._spaceAfter = pS
663        return W, H-pS
664    finally:
665        if cframe:
666            doct.frame = doct_frame
667
668def _flowableSublist(V):
669    "if it isn't a list or tuple, wrap it in a list"
670    if not isinstance(V,(list,tuple)): V = V is not None and [V] or []
671    from reportlab.platypus.doctemplate import LCActionFlowable
672    assert not [x for x in V if isinstance(x,LCActionFlowable)],'LCActionFlowables not allowed in sublists'
673    return V
674
675class _ContainerSpace:  #Abstract some common container like behaviour
676    def getSpaceBefore(self,content=None):
677        for c in (self._content if content is None else content):
678            if not hasattr(c,'frameAction'):
679                return c.getSpaceBefore()
680        return 0
681
682    def getSpaceAfter(self,content=None):
683        for c in reversed(self._content if content is None else content):
684            if not hasattr(c,'frameAction'):
685                return c.getSpaceAfter()
686        return 0
687
688class KeepTogether(_ContainerSpace,Flowable):
689    splitAtTop = False
690
691    def __init__(self,flowables,maxHeight=None):
692        if not hasattr(KeepTogether,'NullActionFlowable'):
693            #cache these on the class
694            from reportlab.platypus.doctemplate import NullActionFlowable
695            from reportlab.platypus.doctemplate import FrameBreak
696            from reportlab.lib.utils import annotateException
697            KeepTogether.NullActionFlowable = NullActionFlowable
698            KeepTogether.FrameBreak = FrameBreak
699            KeepTogether.annotateException = annotateException
700
701        if not flowables:
702            flowables = [self.NullActionFlowable()]
703        self._content = _flowableSublist(flowables)
704        self._maxHeight = maxHeight
705
706    def __repr__(self):
707        f = self._content
708        L = list(map(repr,f))
709        L = "\n"+"\n".join(L)
710        L = L.replace("\n", "\n  ")
711        return "%s(%s,maxHeight=%s)" % (self.__class__.__name__,L,self._maxHeight)
712
713    def wrap(self, aW, aH):
714        dims = []
715        try:
716            W,H = _listWrapOn(self._content,aW,self.canv,dims=dims)
717        except:
718            self.annotateException('\nraised by class %s(%s)@0x%8.8x wrap\n' % (self.__class__.__name__,self.__class__.__module__,id(self)))
719        self._H = H
720        self._H0 = dims and dims[0][1] or 0
721        self._wrapInfo = aW,aH
722        return W, 0xffffff  # force a split
723
724    def split(self, aW, aH):
725        if getattr(self,'_wrapInfo',None)!=(aW,aH): self.wrap(aW,aH)
726        S = self._content[:]
727        cf = atTop = getattr(self,'_frame',None)
728        if cf:
729            atTop = getattr(cf,'_atTop',None)
730            cAW = cf._width
731            cAH = cf._height
732        C0 = self._H>aH and (not self._maxHeight or aH>self._maxHeight)
733        C1 = (self._H0>aH) or C0 and atTop
734        if C0 or C1:
735            fb = False
736            panf = self._doctemplateAttr('_peekNextFrame')
737            if cf and panf:
738                nf = panf()
739                nAW = nf._width
740                nAH = nf._height
741            if C0 and not (self.splitAtTop and atTop):
742                fb = not (atTop and cf and nf and cAW>=nAW and cAH>=nAH)
743            elif nf and nAW>=cf._width and nAH>=self._H:
744                fb = True
745
746            S.insert(0,(self.FrameBreak if fb else self.NullActionFlowable)())
747        return S
748
749    def identity(self, maxLen=None):
750        msg = "<%s at %s%s> containing :%s" % (self.__class__.__name__,hex(id(self)),self._frameName(),"\n".join([f.identity() for f in self._content]))
751        if maxLen:
752            return msg[0:maxLen]
753        else:
754            return msg
755
756class KeepTogetherSplitAtTop(KeepTogether):
757    '''
758    Same as KeepTogether, but it will split content immediately if it cannot
759    fit at the top of a frame.
760    '''
761    splitAtTop = True
762
763class Macro(Flowable):
764    """This is not actually drawn (i.e. it has zero height)
765    but is executed when it would fit in the frame.  Allows direct
766    access to the canvas through the object 'canvas'"""
767    def __init__(self, command):
768        self.command = command
769    def __repr__(self):
770        return "Macro(%s)" % repr(self.command)
771    def wrap(self, availWidth, availHeight):
772        return (0,0)
773    def draw(self):
774        rl_safe_exec(self.command, g=None, l={'canvas':self.canv})
775
776def _nullCallable(*args,**kwds):
777    pass
778
779class CallerMacro(Flowable):
780    '''
781    like Macro, but with callable command(s)
782    drawCallable(self)
783    wrapCallable(self,aW,aH)
784    '''
785    def __init__(self, drawCallable=None, wrapCallable=None):
786        self._drawCallable = drawCallable or _nullCallable
787        self._wrapCallable = wrapCallable or _nullCallable
788    def __repr__(self):
789        return "CallerMacro(%r,%r)" % (self._drawCallable,self._wrapCallable)
790    def wrap(self, aW, aH):
791        self._wrapCallable(self,aW,aH)
792        return (0,0)
793    def draw(self):
794        self._drawCallable(self)
795
796class ParagraphAndImage(Flowable):
797    '''combine a Paragraph and an Image'''
798    def __init__(self,P,I,xpad=3,ypad=3,side='right'):
799        self.P = P
800        self.I = I
801        self.xpad = xpad
802        self.ypad = ypad
803        self._side = side
804
805    def getSpaceBefore(self):
806        return max(self.P.getSpaceBefore(),self.I.getSpaceBefore())
807
808    def getSpaceAfter(self):
809        return max(self.P.getSpaceAfter(),self.I.getSpaceAfter())
810
811    def wrap(self,availWidth,availHeight):
812        wI, hI = self.I.wrap(availWidth,availHeight)
813        self.wI = wI
814        self.hI = hI
815        # work out widths array for breaking
816        self.width = availWidth
817        P = self.P
818        style = P.style
819        xpad = self.xpad
820        ypad = self.ypad
821        leading = style.leading
822        leftIndent = style.leftIndent
823        later_widths = availWidth - leftIndent - style.rightIndent
824        intermediate_widths = later_widths - xpad - wI
825        first_line_width = intermediate_widths - style.firstLineIndent
826        P.width = 0
827        nIW = int((hI+ypad)/(leading*1.0))
828        P.blPara = P.breakLines([first_line_width] + nIW*[intermediate_widths]+[later_widths])
829        if self._side=='left':
830            self._offsets = [wI+xpad]*(1+nIW)+[0]
831        P.height = len(P.blPara.lines)*leading
832        self.height = max(hI,P.height)
833        return (self.width, self.height)
834
835    def split(self,availWidth, availHeight):
836        P, wI, hI, ypad = self.P, self.wI, self.hI, self.ypad
837        if hI+ypad>availHeight or len(P.frags)<=0: return []
838        S = P.split(availWidth,availHeight)
839        if not S: return S
840        P = self.P = S[0]
841        del S[0]
842        style = P.style
843        P.height = len(self.P.blPara.lines)*style.leading
844        self.height = max(hI,P.height)
845        return [self]+S
846
847    def draw(self):
848        canv = self.canv
849        if self._side=='left':
850            self.I.drawOn(canv,0,self.height-self.hI-self.ypad)
851            self.P._offsets = self._offsets
852            try:
853                self.P.drawOn(canv,0,0)
854            finally:
855                del self.P._offsets
856        else:
857            self.I.drawOn(canv,self.width-self.wI-self.xpad,self.height-self.hI-self.ypad)
858            self.P.drawOn(canv,0,0)
859
860class FailOnWrap(NullDraw):
861    def wrap(self, availWidth, availHeight):
862        raise ValueError("FailOnWrap flowable wrapped and failing as ordered!")
863
864class FailOnDraw(Flowable):
865    def wrap(self, availWidth, availHeight):
866        return 0,0
867
868    def draw(self):
869        raise ValueError("FailOnDraw flowable drawn, and failing as ordered!")
870
871class HRFlowable(Flowable):
872    '''Like the hr tag'''
873    def __init__(self,
874            width="80%",
875            thickness=1,
876            lineCap='round',
877            color=lightgrey,
878            spaceBefore=1, spaceAfter=1,
879            hAlign='CENTER', vAlign='BOTTOM',
880            dash=None):
881        Flowable.__init__(self)
882        self.width = width
883        self.lineWidth = thickness
884        self.lineCap=lineCap
885        self.spaceBefore = spaceBefore
886        self.spaceAfter = spaceAfter
887        self.color = color
888        self.hAlign = hAlign
889        self.vAlign = vAlign
890        self.dash = dash
891
892    def __repr__(self):
893        return "HRFlowable(width=%s, height=%s)" % (self.width, self.height)
894
895    def wrap(self, availWidth, availHeight):
896        w = self.width
897        if isinstance(w,strTypes):
898            w = w.strip()
899            if w.endswith('%'): w = availWidth*float(w[:-1])*0.01
900            else: w = float(w)
901        w = min(w,availWidth)
902        self._width = w
903        return w, self.lineWidth
904
905    def draw(self):
906        canv = self.canv
907        canv.saveState()
908        canv.setLineWidth(self.lineWidth)
909        canv.setLineCap({'butt':0,'round':1, 'square': 2}[self.lineCap.lower()])
910        canv.setStrokeColor(self.color)
911        if self.dash: canv.setDash(self.dash)
912        canv.line(0, 0, self._width, self.height)
913        canv.restoreState()
914
915class _PTOInfo:
916    def __init__(self,trailer,header):
917        self.trailer = _flowableSublist(trailer)
918        self.header = _flowableSublist(header)
919
920def cdeepcopy(obj):
921    if hasattr(obj,'deepcopy'):
922        return obj.deepcopy()
923    else:
924        return deepcopy(obj)
925
926class _Container(_ContainerSpace):  #Abstract some common container like behaviour
927    def drawOn(self, canv, x, y, _sW=0, scale=1.0, content=None, aW=None):
928        '''we simulate being added to a frame'''
929        from reportlab.platypus.doctemplate import ActionFlowable, Indenter
930        x0 = x
931        y0 = y
932        pS = 0
933        if aW is None: aW = self.width
934        aW *= scale
935        if content is None:
936            content = self._content
937        x = self._hAlignAdjust(x,_sW*scale)
938        y += self.height*scale
939        yt = y
940        frame = getattr(self,'_frame',None)
941        for c in content:
942            if not ignoreContainerActions and isinstance(c,ActionFlowable):
943                c.apply(canv._doctemplate)
944                continue
945            if isinstance(c,Indenter):
946                x += c.left*scale
947                aW -= (c.left+c.right)*scale
948                continue
949            w, h = c.wrapOn(canv,aW,0xfffffff)
950            if (w<_FUZZ or h<_FUZZ) and not getattr(c,'_ZEROSIZE',None): continue
951            if yt!=y:
952                s = c.getSpaceBefore()
953                if not getattr(c,'_SPACETRANSFER',False):
954                    h += max(s-pS,0)
955            y -= h
956            s = c.getSpaceAfter()
957            if getattr(c,'_SPACETRANSFER',False):
958                s = pS
959            pS = s
960            fbg = getattr(frame,'_frameBGs',None)
961            if fbg and fbg[-1].active:
962                bg = fbg[-1]
963                fbgl = bg.left
964                fbgr = bg.right
965                bgm = bg.start
966                fbw = scale*(frame._width-fbgl-fbgr)
967                fbx = x0+scale*(fbgl-frame._leftPadding)
968                fbh = y + h + pS
969                fby = max(y0,y-pS)
970                fbh = max(0,fbh-fby)
971                bg.render(canv,frame,fbx,fby,fbw,fbh)
972            c._frame = frame
973            c.drawOn(canv,x,y,_sW=aW-w)
974            if c is not content[-1] and not getattr(c,'_SPACETRANSFER',None):
975                y -= pS
976            del c._frame
977
978    def copyContent(self,content=None):
979        C = [].append
980        for c in (content or self._content):
981            C(cdeepcopy(c))
982        self._content = C.__self__
983
984class PTOContainer(_Container,Flowable):
985    '''PTOContainer(contentList,trailerList,headerList)
986
987    A container for flowables decorated with trailer & header lists.
988    If the split operation would be called then the trailer and header
989    lists are injected before and after the split. This allows specialist
990    "please turn over" and "continued from previous" like behaviours.'''
991    def __init__(self,content,trailer=None,header=None):
992        I = _PTOInfo(trailer,header)
993        self._content = C = []
994        for _ in _flowableSublist(content):
995            if isinstance(_,PTOContainer):
996                C.extend(_._content)
997            else:
998                C.append(_)
999                if not hasattr(_,'_ptoinfo'): _._ptoinfo = I
1000
1001    def wrap(self,availWidth,availHeight):
1002        self.width, self.height = _listWrapOn(self._content,availWidth,self.canv)
1003        return self.width,self.height
1004
1005    def split(self, availWidth, availHeight):
1006        from reportlab.platypus.doctemplate import Indenter
1007        if availHeight<0: return []
1008        canv = self.canv
1009        C = self._content
1010        x = i = H = pS = hx = 0
1011        n = len(C)
1012        I2W = {}
1013        dLeft = dRight = 0
1014        for x in xrange(n):
1015            c = C[x]
1016            I = c._ptoinfo
1017            if I not in I2W.keys():
1018                T = I.trailer
1019                Hdr = I.header
1020                tW, tH = _listWrapOn(T, availWidth, self.canv)
1021                if len(T):  #trailer may have no content
1022                    tSB = T[0].getSpaceBefore()
1023                else:
1024                    tSB = 0
1025                I2W[I] = T,tW,tH,tSB
1026            else:
1027                T,tW,tH,tSB = I2W[I]
1028            _, h = c.wrapOn(canv,availWidth,0xfffffff)
1029            if isinstance(c,Indenter):
1030                dw = c.left+c.right
1031                dLeft += c.left
1032                dRight += c.right
1033                availWidth -= dw
1034                pS = 0
1035                hx = 0
1036            else:
1037                if x:
1038                    hx = max(c.getSpaceBefore()-pS,0)
1039                    h += hx
1040                pS = c.getSpaceAfter()
1041            H += h+pS
1042            tHS = tH+max(tSB,pS)
1043            if H+tHS>=availHeight-_FUZZ: break
1044            i += 1
1045
1046        #first retract last thing we tried
1047        H -= (h+pS)
1048
1049        #attempt a sub split on the last one we have
1050        aH = (availHeight-H-tHS-hx)*0.99999
1051        if aH>=0.05*availHeight:
1052            SS = c.splitOn(canv,availWidth,aH)
1053        else:
1054            SS = []
1055
1056        if abs(dLeft)+abs(dRight)>1e-8:
1057            R1I = [Indenter(-dLeft,-dRight)]
1058            R2I = [Indenter(dLeft,dRight)]
1059        else:
1060            R1I = R2I = []
1061
1062        if not SS:
1063            j = i
1064            while i>1 and C[i-1].getKeepWithNext():
1065                i -= 1
1066                C[i].keepWithNext = 0
1067
1068            if i==1 and C[0].getKeepWithNext():
1069                #robin's black sheep
1070                i = j
1071                C[0].keepWithNext = 0
1072
1073        F = [UseUpSpace()]
1074
1075        if len(SS)>1:
1076            R1 = C[:i]+SS[:1]+R1I+T+F
1077            R2 = Hdr+R2I+SS[1:]+C[i+1:]
1078        elif not i:
1079            return []
1080        else:
1081            R1 = C[:i]+R1I+T+F
1082            R2 = Hdr+R2I+C[i:]
1083        T =  R1 + [PTOContainer(R2,[copy(x) for x in I.trailer],[copy(x) for x in I.header])]
1084        return T
1085
1086#utility functions used by KeepInFrame
1087def _hmodel(s0,s1,h0,h1):
1088    # calculate the parameters in the model
1089    # h = a/s**2 + b/s
1090    a11 = 1./s0**2
1091    a12 = 1./s0
1092    a21 = 1./s1**2
1093    a22 = 1./s1
1094    det = a11*a22-a12*a21
1095    b11 = a22/det
1096    b12 = -a12/det
1097    b21 = -a21/det
1098    b22 = a11/det
1099    a = b11*h0+b12*h1
1100    b = b21*h0+b22*h1
1101    return a,b
1102
1103def _qsolve(h,ab):
1104    '''solve the model v = a/s**2 + b/s for an s which gives us v==h'''
1105    a,b = ab
1106    if abs(a)<=_FUZZ:
1107        return b/h
1108    t = 0.5*b/a
1109    from math import sqrt
1110    f = -h/a
1111    r = t*t-f
1112    if r<0: return None
1113    r = sqrt(r)
1114    if t>=0:
1115        s1 = -t - r
1116    else:
1117        s1 = -t + r
1118    s2 = f/s1
1119    return max(1./s1, 1./s2)
1120
1121class KeepInFrame(_Container,Flowable):
1122    def __init__(self, maxWidth, maxHeight, content=[], mergeSpace=1, mode='shrink', name='',hAlign='LEFT',vAlign='BOTTOM', fakeWidth=None):
1123        '''mode describes the action to take when overflowing
1124            error       raise an error in the normal way
1125            continue    ignore ie just draw it and report maxWidth, maxHeight
1126            shrink      shrinkToFit
1127            truncate    fit as much as possible
1128            set fakeWidth to False to make _listWrapOn do the 'right' thing
1129        '''
1130        self.name = name
1131        self.maxWidth = maxWidth
1132        self.maxHeight = maxHeight
1133        self.mode = mode
1134        assert mode in ('error','overflow','shrink','truncate'), '%s invalid mode value %s' % (self.identity(),mode)
1135        assert maxHeight>=0,  '%s invalid maxHeight value %s' % (self.identity(),maxHeight)
1136        if mergeSpace is None: mergeSpace = overlapAttachedSpace
1137        self.mergespace = mergeSpace
1138        self._content = content or []
1139        self.vAlign = vAlign
1140        self.hAlign = hAlign
1141        self.fakeWidth = fakeWidth
1142
1143    def _getAvailableWidth(self):
1144        return self.maxWidth - self._leftExtraIndent - self._rightExtraIndent
1145
1146    def identity(self, maxLen=None):
1147        return "<%s at %s%s%s> size=%sx%s" % (self.__class__.__name__, hex(id(self)), self._frameName(),
1148                getattr(self,'name','') and (' name="%s"'% getattr(self,'name','')) or '',
1149                getattr(self,'maxWidth','') and (' maxWidth=%s'%fp_str(getattr(self,'maxWidth',0))) or '',
1150                getattr(self,'maxHeight','')and (' maxHeight=%s' % fp_str(getattr(self,'maxHeight')))or '')
1151
1152    def wrap(self,availWidth,availHeight):
1153        from reportlab.platypus.doctemplate import LayoutError
1154        mode = self.mode
1155        maxWidth = float(min(self.maxWidth or availWidth,availWidth))
1156        maxHeight = float(min(self.maxHeight or availHeight,availHeight))
1157        fakeWidth = self.fakeWidth
1158        W, H = _listWrapOn(self._content,maxWidth,self.canv, fakeWidth=fakeWidth)
1159        if (mode=='error' and (W>maxWidth+_FUZZ or H>maxHeight+_FUZZ)):
1160            ident = 'content %sx%s too large for %s' % (W,H,self.identity(30))
1161            #leave to keep apart from the raise
1162            raise LayoutError(ident)
1163        elif W<=maxWidth+_FUZZ and H<=maxHeight+_FUZZ:
1164            self.width = W-_FUZZ      #we take what we get
1165            self.height = H-_FUZZ
1166        elif mode in ('overflow','truncate'):   #we lie
1167            self.width = min(maxWidth,W)-_FUZZ
1168            self.height = min(maxHeight,H)-_FUZZ
1169        else:
1170            def func(x):
1171                x = float(x)
1172                W, H = _listWrapOn(self._content,x*maxWidth,self.canv, fakeWidth=fakeWidth)
1173                W /= x
1174                H /= x
1175                return W, H
1176            W0 = W
1177            H0 = H
1178            s0 = 1
1179            if W>maxWidth+_FUZZ:
1180                #squeeze out the excess width and or Height
1181                s1 = W/maxWidth     #linear model
1182                W, H = func(s1)
1183                if H<=maxHeight+_FUZZ:
1184                    self.width = W-_FUZZ
1185                    self.height = H-_FUZZ
1186                    self._scale = s1
1187                    return W,H
1188                s0 = s1
1189                H0 = H
1190                W0 = W
1191            s1 = H/maxHeight
1192            W, H = func(s1)
1193            self.width = W-_FUZZ
1194            self.height = H-_FUZZ
1195            self._scale = s1
1196            if H<min(0.95*maxHeight,maxHeight-10) or H>=maxHeight+_FUZZ:
1197                #the standard case W should be OK, H is short we want
1198                #to find the smallest s with H<=maxHeight
1199                H1 = H
1200                for f in 0, 0.01, 0.05, 0.10, 0.15:
1201                    #apply the quadratic model
1202                    s = _qsolve(maxHeight*(1-f),_hmodel(s0,s1,H0,H1))
1203                    W, H = func(s)
1204                    if H<=maxHeight+_FUZZ and W<=maxWidth+_FUZZ:
1205                        self.width = W-_FUZZ
1206                        self.height = H-_FUZZ
1207                        self._scale = s
1208                        break
1209
1210        return self.width, self.height
1211
1212    def drawOn(self, canv, x, y, _sW=0):
1213        scale = getattr(self,'_scale',1.0)
1214        truncate = self.mode=='truncate'
1215        ss = scale!=1.0 or truncate
1216        if ss:
1217            canv.saveState()
1218            if truncate:
1219                p = canv.beginPath()
1220                p.rect(x, y, self.width,self.height)
1221                canv.clipPath(p,stroke=0)
1222            else:
1223                canv.translate(x,y)
1224                x=y=0
1225                canv.scale(1.0/scale, 1.0/scale)
1226        _Container.drawOn(self, canv, x, y, _sW=_sW, scale=scale)
1227        if ss: canv.restoreState()
1228
1229
1230class _FindSplitterMixin:
1231    def _findSplit(self,canv,availWidth,availHeight,mergeSpace=1,obj=None,content=None,paraFix=True):
1232        '''return max width, required height for a list of flowables F'''
1233        W = 0
1234        H = 0
1235        pS = sB = 0
1236        atTop = 1
1237        F = self._getContent(content)
1238        for i,f in enumerate(F):
1239            if hasattr(f,'frameAction'):
1240                from reportlab.platypus.doctemplate import Indenter
1241                if isinstance(f,Indenter):
1242                    availWidth -= f.left+f.right
1243                continue
1244            w,h = f.wrapOn(canv,availWidth,0xfffffff)
1245            if w<=_FUZZ or h<=_FUZZ: continue
1246            W = max(W,w)
1247            if not atTop:
1248                s = f.getSpaceBefore()
1249                if mergeSpace: s = max(s-pS,0)
1250                H += s
1251            else:
1252                if obj is not None: obj._spaceBefore = f.getSpaceBefore()
1253                atTop = 0
1254            if H>=availHeight or w>availWidth:
1255                return W, availHeight, F[:i],F[i:]
1256            H += h
1257            if H>availHeight:
1258                aH = availHeight-(H-h)
1259                if paraFix:
1260                    from reportlab.platypus.paragraph import Paragraph
1261                    if isinstance(f,(Paragraph,Preformatted)):
1262                        leading = f.style.leading
1263                        nH = leading*int(aH/float(leading))+_FUZZ
1264                        if nH<aH: nH += leading
1265                        availHeight += nH-aH
1266                        aH = nH
1267                S = cdeepcopy(f).splitOn(canv,availWidth,aH)
1268                if not S:
1269                    return W, availHeight, F[:i],F[i:]
1270                else:
1271                    return W,availHeight,F[:i]+S[:1],S[1:]+F[i+1:]
1272            pS = f.getSpaceAfter()
1273            H += pS
1274
1275        if obj is not None: obj._spaceAfter = pS
1276        return W, H-pS, F, []
1277
1278    def _getContent(self,content=None):
1279        F = []
1280        C = content if content is not None else self._content
1281        for f in C:
1282            if isinstance(f,ListFlowable):
1283                F.extend(self._getContent(f._content))
1284            else:
1285                F.append(f)
1286        return F
1287
1288class ImageAndFlowables(_Container,_FindSplitterMixin,Flowable):
1289    '''combine a list of flowables and an Image'''
1290    def __init__(self,I,F,imageLeftPadding=0,imageRightPadding=3,imageTopPadding=0,imageBottomPadding=3,
1291                    imageSide='right', imageHref=None):
1292        self._content = _flowableSublist(F)
1293        self._I = I
1294        self._irpad = imageRightPadding
1295        self._ilpad = imageLeftPadding
1296        self._ibpad = imageBottomPadding
1297        self._itpad = imageTopPadding
1298        self._side = imageSide
1299        self.imageHref = imageHref
1300
1301    def deepcopy(self):
1302        c = copy(self)  #shallow
1303        self._reset()
1304        c.copyContent() #partially deep?
1305        return c
1306
1307    def getSpaceAfter(self):
1308        if hasattr(self,'_C1'):
1309            C = self._C1
1310        elif hasattr(self,'_C0'):
1311            C = self._C0
1312        else:
1313            C = self._content
1314        return _Container.getSpaceAfter(self,C)
1315
1316    def getSpaceBefore(self):
1317        return max(self._I.getSpaceBefore(),_Container.getSpaceBefore(self))
1318
1319    def _reset(self):
1320        for a in ('_wrapArgs','_C0','_C1'):
1321            try:
1322                delattr(self,a)
1323            except:
1324                pass
1325
1326    def wrap(self,availWidth,availHeight):
1327        canv = self.canv
1328        I = self._I
1329        if hasattr(self,'_wrapArgs'):
1330            if self._wrapArgs==(availWidth,availHeight) and getattr(I,'_oldDrawSize',None) is None:
1331                return self.width,self.height
1332            self._reset()
1333            I._unRestrictSize()
1334        self._wrapArgs = availWidth, availHeight
1335        I.wrap(availWidth,availHeight)
1336        wI, hI = I._restrictSize(availWidth,availHeight)
1337        self._wI = wI
1338        self._hI = hI
1339        ilpad = self._ilpad
1340        irpad = self._irpad
1341        ibpad = self._ibpad
1342        itpad = self._itpad
1343        self._iW = iW = availWidth - irpad - wI - ilpad
1344        aH = itpad + hI + ibpad
1345        if iW>_FUZZ:
1346            W,H0,self._C0,self._C1 = self._findSplit(canv,iW,aH)
1347        else:
1348            W = availWidth
1349            H0 = 0
1350        if W>iW+_FUZZ:
1351            self._C0 = []
1352            self._C1 = self._content
1353        aH = self._aH = max(aH,H0)
1354        self.width = availWidth
1355        if not self._C1:
1356            self.height = aH
1357        else:
1358            W1,H1 = _listWrapOn(self._C1,availWidth,canv)
1359            self.height = aH+H1
1360        return self.width, self.height
1361
1362    def split(self,availWidth, availHeight):
1363        if hasattr(self,'_wrapArgs'):
1364            I = self._I
1365            if self._wrapArgs!=(availWidth,availHeight) or getattr(I,'_oldDrawSize',None) is not None:
1366                self._reset()
1367                I._unRestrictSize()
1368        W,H=self.wrap(availWidth,availHeight)
1369        if self._aH>availHeight: return []
1370        C1 = self._C1
1371        if C1:
1372            S = C1[0].split(availWidth,availHeight-self._aH)
1373            if not S:
1374                _C1 = []
1375            else:
1376                _C1 = [S[0]]
1377                C1 = S[1:]+C1[1:]
1378        else:
1379            _C1 = []
1380        return [ImageAndFlowables(
1381                    self._I,
1382                    self._C0+_C1,
1383                    imageLeftPadding=self._ilpad,
1384                    imageRightPadding=self._irpad,
1385                    imageTopPadding=self._itpad,
1386                    imageBottomPadding=self._ibpad,
1387                    imageSide=self._side, imageHref=self.imageHref)
1388                    ]+C1
1389
1390    def drawOn(self, canv, x, y, _sW=0):
1391        if self._side=='left':
1392            Ix = x + self._ilpad
1393            Fx = Ix+ self._irpad + self._wI
1394        else:
1395            Ix = x + self.width-self._wI-self._irpad
1396            Fx = x
1397        self._I.drawOn(canv,Ix,y+self.height-self._itpad-self._hI)
1398
1399        if self.imageHref:
1400            canv.linkURL(self.imageHref, (Ix, y+self.height-self._itpad-self._hI, Ix + self._wI, y+self.height), relative=1)
1401
1402        if self._C0:
1403            _Container.drawOn(self, canv, Fx, y, content=self._C0, aW=self._iW)
1404        if self._C1:
1405            aW, aH = self._wrapArgs
1406            _Container.drawOn(self, canv, x, y-self._aH,content=self._C1, aW=aW)
1407
1408class _AbsRect(NullDraw):
1409    _ZEROSIZE=1
1410    _SPACETRANSFER = True
1411    def __init__(self,x,y,width,height,strokeWidth=0,strokeColor=None,fillColor=None,strokeDashArray=None):
1412        self._x = x
1413        self._y = y
1414        self._width = width
1415        self._height = height
1416        self._strokeColor = strokeColor
1417        self._fillColor = fillColor
1418        self._strokeWidth = strokeWidth
1419        self._strokeDashArray = strokeDashArray
1420
1421    def wrap(self, availWidth, availHeight):
1422        return 0,0
1423
1424    def drawOn(self, canv, x, y, _sW=0):
1425        if self._width>_FUZZ and self._height>_FUZZ:
1426            st = self._strokeColor and self._strokeWidth is not None and self._strokeWidth>=0
1427            if st or self._fillColor:
1428                canv.saveState()
1429                if st:
1430                    canv.setStrokeColor(self._strokeColor)
1431                    canv.setLineWidth(self._strokeWidth)
1432                if self._fillColor:
1433                    canv.setFillColor(self._fillColor)
1434                canv.rect(self._x,self._y,self._width,self._height,stroke=1 if st else 0, fill=1 if self._fillColor else 0)
1435                canv.restoreState()
1436
1437class _ExtendBG(NullDraw):
1438    _ZEROSIZE=1
1439    _SPACETRANSFER = True
1440    def __init__(self,y,height,bg,frame):
1441        self._y = y
1442        self._height = height
1443        self._bg = bg
1444
1445    def wrap(self, availWidth, availHeight):
1446        return 0,0
1447
1448    def frameAction(self, frame):
1449        bg = self._bg
1450        fby = self._y
1451        fbh = self._height
1452        fbgl = bg.left
1453        fbw = frame._width - fbgl - bg.right
1454        fbx = frame._x1 - fbgl
1455        canv = self.canv
1456        pn = canv.getPageNumber()
1457        bg.render(canv,frame,fbx,fby,fbw,fbh)
1458
1459class _AbsLine(NullDraw):
1460    _ZEROSIZE=1
1461    _SPACETRANSFER = True
1462    def __init__(self,x,y,x1,y1,strokeWidth=0,strokeColor=None,strokeDashArray=None):
1463        self._x = x
1464        self._y = y
1465        self._x1 = x1
1466        self._y1 = y1
1467        self._strokeColor = strokeColor
1468        self._strokeWidth = strokeWidth
1469        self._strokeDashArray = strokeDashArray
1470
1471    def wrap(self, availWidth, availHeight):
1472        return 0,0
1473
1474    def drawOn(self, canv, x, y, _sW=0):
1475        if self._strokeColor and self._strokeWidth is not None and self._strokeWidth>=0:
1476            canv.saveState()
1477            canv.setStrokeColor(self._strokeColor)
1478            canv.setLineWidth(self._strokeWidth)
1479            canv.line(self._x,self._y,self._x1,self._y1)
1480            canv.restoreState()
1481
1482class BalancedColumns(_FindSplitterMixin,NullDraw):
1483    '''combine a list of flowables and an Image'''
1484    def __init__(self, F, nCols=2, needed=72, spaceBefore=0, spaceAfter=0, showBoundary=None,
1485            leftPadding=None, innerPadding=None, rightPadding=None, topPadding=None, bottomPadding=None,
1486            name='', endSlack=0.1,
1487            boxStrokeColor=None,
1488            boxStrokeWidth=0,
1489            boxFillColor=None,
1490            boxMargin=None,
1491            vLinesStrokeColor=None,
1492            vLinesStrokeWidth=None,
1493            ):
1494        self.name = name or 'BalancedColumns-%d' % id(self)
1495        if nCols <2:
1496            raise ValueError('nCols should be at least 2 not %r in %s' % (nCols,self.identitity()))
1497        self._content = _flowableSublist(F)
1498        self._nCols = nCols
1499        self.spaceAfter = spaceAfter
1500        self._leftPadding = leftPadding
1501        self._innerPadding = innerPadding
1502        self._rightPadding = rightPadding
1503        self._topPadding = topPadding
1504        self._bottomPadding = bottomPadding
1505        self.spaceBefore = spaceBefore
1506        self._needed = needed - _FUZZ
1507        self.showBoundary = showBoundary
1508        self.endSlack = endSlack    #what we might allow as a lastcolumn overrun
1509        self._boxStrokeColor = boxStrokeColor
1510        self._boxStrokeWidth = boxStrokeWidth
1511        self._boxFillColor = boxFillColor
1512        self._boxMargin = boxMargin
1513        self._vLinesStrokeColor = vLinesStrokeColor
1514        self._vLinesStrokeWidth = vLinesStrokeWidth
1515
1516    def identity(self, maxLen=None):
1517        return "<%s nCols=%r at %s%s%s>" % (self.__class__.__name__, self._nCols, hex(id(self)), self._frameName(),
1518                getattr(self,'name','') and (' name="%s"'% getattr(self,'name','')) or '',
1519                )
1520
1521    def getSpaceAfter(self):
1522        return self.spaceAfter
1523
1524    def getSpaceBefore(self):
1525        return self.spaceBefore
1526
1527    def _generated_content(self,aW,aH):
1528        G = []
1529        frame = self._frame
1530        from reportlab.platypus.doctemplate import CurrentFrameFlowable,LayoutError, ActionFlowable, Indenter
1531        from reportlab.platypus.frames import Frame
1532        from reportlab.platypus.doctemplate import FrameBreak
1533        lpad = frame._leftPadding if self._leftPadding is None else self._leftPadding
1534        rpad = frame._rightPadding if self._rightPadding is None else self._rightPadding
1535        tpad = frame._topPadding if self._topPadding is None else self._topPadding
1536        bpad = frame._bottomPadding if self._bottomPadding is None else self._bottomPadding
1537        leftExtraIndent = frame._leftExtraIndent
1538        rightExtraIndent = frame._rightExtraIndent
1539        gap = max(lpad,rpad) if self._innerPadding is None else self._innerPadding
1540        hgap = gap*0.5
1541        canv = self.canv
1542        nCols = self._nCols
1543        cw = (aW - gap*(nCols-1) - lpad - rpad)/float(nCols)
1544        aH0 = aH
1545        aH -= tpad + bpad
1546        W,H0,_C0,C2 = self._findSplit(canv,cw,nCols*aH,paraFix=False)
1547        if not _C0:
1548            raise ValueError(
1549                    "%s cannot make initial split aW=%r aH=%r ie cw=%r ah=%r\ncontent=%s" % (
1550                        self.identity(),aW,aH,cw,nCols*aH,
1551                        [f.__class__.__name__ for f in self._content],
1552                        ))
1553        _fres = {}
1554        def splitFunc(ah,endSlack=0):
1555            if ah not in _fres:
1556                c = []
1557                w = 0
1558                h = 0
1559                cn = None
1560                icheck = nCols-2 if endSlack else -1
1561                for i in xrange(nCols):
1562                    wi, hi, c0, c1 = self._findSplit(canv,cw,ah,content=cn,paraFix=False)
1563                    w = max(w,wi)
1564                    h = max(h,hi)
1565                    c.append(c0)
1566                    if i==icheck:
1567                        wc, hc, cc0, cc1 = self._findSplit(canv,cw,2*ah,content=c1,paraFix=False)
1568                        if hc<=(1+endSlack)*ah:
1569                            c.append(c1)
1570                            h = ah-1e-6
1571                            cn = []
1572                            break
1573                    cn = c1
1574                _fres[ah] = ah+100000*int(cn!=[]),cn==[],(w,h,c,cn)
1575            return _fres[ah][2]
1576
1577        endSlack = 0
1578        if C2:
1579            H = aH
1580        else:
1581            #we are short so use H0 to figure out what to use
1582            import math
1583
1584            def func(ah):
1585                splitFunc(ah)
1586                return _fres[ah][0]
1587
1588            def gss(f, a, b, tol=1, gr=(math.sqrt(5) + 1) / 2):
1589                c = b - (b - a) / gr
1590                d = a + (b - a) / gr
1591                while abs(a - b) > tol:
1592                    if f(c) < f(d):
1593                        b = d
1594                    else:
1595                        a = c
1596
1597                    # we recompute both c and d here to avoid loss of precision which may lead to incorrect results or infinite loop
1598                    c = b - (b - a) / gr
1599                    d = a + (b - a) / gr
1600
1601                F = [(x,tf,v) for x,tf,v in _fres.values() if tf]
1602                if F:
1603                    F.sort()
1604                    return F[0][2]
1605                return None
1606
1607            H = min(int(H0/float(nCols)+self.spaceAfter*0.4),aH)
1608            splitFunc(H)
1609            if not _fres[H][1]:
1610                H = gss(func,H,aH)
1611                if H:
1612                    W, H0, _C0, C2 = H
1613                    H = H0
1614                    endSlack = False
1615                else:
1616                    H = aH
1617                    endSlack = self.endSlack
1618            else:
1619                H1 = H0/float(nCols)
1620                splitFunc(H1)
1621                if not _fres[H1][1]:
1622                    H = gss(func,H,aH)
1623                    if H:
1624                        W, H0, _C0, C2 = H
1625                        H = H0
1626                        endSlack = False
1627                    else:
1628                        H = aH
1629                        endSlack = self.endSlack
1630            assert not C2, "unexpected non-empty C2"
1631        W1, H1, C, C1 = splitFunc(H, endSlack)
1632        _fres.clear()
1633        if C[0]==[] and C[1]==[] and C1:
1634            #no split situation
1635            C, C1 = [C1,C[1]], C[0]
1636
1637        x1 = frame._x1
1638        y1 = frame._y1
1639        fw = frame._width
1640        ftop = y1+bpad+tpad+aH
1641        fh = H1 + bpad + tpad
1642        y2 = ftop - fh
1643        dx = aW / float(nCols)
1644        if leftExtraIndent or rightExtraIndent:
1645            indenter0 = Indenter(-leftExtraIndent,-rightExtraIndent)
1646            indenter1 = Indenter(leftExtraIndent,rightExtraIndent)
1647        else:
1648            indenter0 = indenter1 = None
1649
1650        showBoundary=self.showBoundary if self.showBoundary is not None else frame.showBoundary
1651        obx = x1+leftExtraIndent+frame._leftPadding
1652        F = [Frame(obx+i*dx,y2,dx,fh,
1653                leftPadding=lpad if not i else hgap, bottomPadding=bpad,
1654                rightPadding=rpad if i==nCols-1 else hgap, topPadding=tpad,
1655                id='%s-%d' %(self.name,i),
1656                showBoundary=showBoundary,
1657                overlapAttachedSpace=frame._oASpace,
1658                _debug=frame._debug) for i in xrange(nCols)]
1659
1660
1661        #we are going to modify the current template
1662        T=self._doctemplateAttr('pageTemplate')
1663        if T is None:
1664            raise LayoutError('%s used in non-doctemplate environment' % self.identity())
1665
1666        BGs = getattr(frame,'_frameBGs',None)
1667        xbg = bg = BGs[-1] if BGs else None
1668
1669        class TAction(ActionFlowable):
1670            '''a special Action flowable that sets stuff on the doc template T'''
1671            def __init__(self, bgs=[],F=[],f=None):
1672                Flowable.__init__(self)
1673                self.bgs = bgs
1674                self.F = F
1675                self.f = f
1676
1677            def apply(self,doc,T=T):
1678                T.frames = self.F
1679                frame._frameBGs = self.bgs
1680                doc.handle_currentFrame(self.f.id)
1681                frame._frameBGs = self.bgs
1682
1683        if bg:
1684            #G.append(Spacer(1e-5,1e-5))
1685            #G[-1].__id__ = 'spacer0'
1686            xbg = _ExtendBG(y2,fh,bg,frame)
1687            G.append(xbg)
1688
1689        oldFrames = T.frames
1690        G.append(TAction([],F,F[0]))
1691        if indenter0: G.append(indenter0)
1692        doBox = (self._boxStrokeColor and self._boxStrokeWidth and self._boxStrokeWidth>=0) or self._boxFillColor
1693        doVLines = self._vLinesStrokeColor and self._vLinesStrokeWidth and self._vLinesStrokeWidth>=0
1694        if doBox or doVLines:
1695            obm = self._boxMargin
1696            if not obm: obm = (0,0,0,0)
1697            if len(obm)==1:
1698                obmt = obml = obmr = obmb = obm[0]
1699            elif len(obm)==2:
1700                obmt = obmb = obm[0]
1701                obml = obmr = obm[1]
1702            elif len(obm)==3:
1703                obmt = obm[0]
1704                obml = obmr = obm[1]
1705                obmb = obm[2]
1706            elif len(obm)==4:
1707                obmt = obm[0]
1708                obmr = obm[1]
1709                obmb = obm[2]
1710                obml = obm[3]
1711            else:
1712                raise ValueError('Invalid value %s for boxMargin' % repr(obm))
1713            obx1 = obx - obml
1714            obx2 = F[-1]._x1+F[-1]._width + obmr
1715            oby2 = y2-obmb
1716            obh = fh+obmt+obmb
1717            oby1 = oby2+obh
1718            if doBox:
1719                box = _AbsRect(obx1,oby2, obx2-obx1, obh,
1720                        fillColor=self._boxFillColor,
1721                        strokeColor=self._boxStrokeColor,
1722                        strokeWidth=self._boxStrokeWidth,
1723                        )
1724            if doVLines:
1725                vLines = []
1726                for i in xrange(1,nCols):
1727                    vlx = 0.5*(F[i]._x1 + F[i-1]._x1+F[i-1]._width)
1728                    vLines.append(_AbsLine(vlx,oby2,vlx,oby1,strokeWidth=self._vLinesStrokeWidth,strokeColor=self._vLinesStrokeColor))
1729        else:
1730            oby1 = ftop
1731            oby2 = y2
1732
1733        if doBox: G.append(box)
1734        if doVLines: G.extend(vLines)
1735        sa = self.getSpaceAfter()
1736        for i in xrange(nCols):
1737            Ci = C[i]
1738            if Ci:
1739                Ci = KeepInFrame(W1,H1,Ci,mode='shrink')
1740                sa = max(sa,Ci.getSpaceAfter())
1741                G.append(Ci)
1742            if i!=nCols-1:
1743                G.append(FrameBreak)
1744        G.append(TAction(BGs,oldFrames,frame))
1745        if xbg:
1746            if C1: sa = 0
1747            xbg._y = min(y2,oby2) - sa
1748            xbg._height = max(ftop,oby1) - xbg._y
1749        if indenter1: G.append(indenter1)
1750        if C1:
1751            G.append(
1752                BalancedColumns(C1, nCols=nCols,
1753                    needed=self._needed, spaceBefore=self.spaceBefore, spaceAfter=self.spaceAfter,
1754                    showBoundary=self.showBoundary,
1755                    leftPadding=self._leftPadding,
1756                    innerPadding=self._innerPadding,
1757                    rightPadding=self._rightPadding,
1758                    topPadding=self._topPadding,
1759                    bottomPadding=self._bottomPadding,
1760                    name=self.name+'-1', endSlack=self.endSlack,
1761                    boxStrokeColor=self._boxStrokeColor,
1762                    boxStrokeWidth=self._boxStrokeWidth,
1763                    boxFillColor=self._boxFillColor,
1764                    boxMargin=self._boxMargin,
1765                    vLinesStrokeColor=self._vLinesStrokeColor,
1766                    vLinesStrokeWidth=self._vLinesStrokeWidth,
1767                    )
1768                )
1769        return fh, G
1770
1771    def wrap(self,aW,aH):
1772        #here's where we mess with everything
1773        if aH<self.spaceBefore+self._needed-_FUZZ:
1774            #we are going straight to the nextTemplate with no attempt to modify the frames
1775            G = [PageBreak(), self]
1776            H1 = 0
1777        else:
1778            H1, G = self._generated_content(aW,aH)
1779
1780        self._frame.add_generated_content(*G)
1781        return 0,min(H1,aH)
1782
1783class AnchorFlowable(Spacer):
1784    '''create a bookmark in the pdf'''
1785    _ZEROSIZE=1
1786    _SPACETRANSFER = True
1787    def __init__(self,name):
1788        Spacer.__init__(self,0,0)
1789        self._name = name
1790
1791    def __repr__(self):
1792        return "%s(%s)" % (self.__class__.__name__,self._name)
1793
1794    def wrap(self,aW,aH):
1795        return 0,0
1796
1797    def draw(self):
1798        self.canv.bookmarkHorizontal(self._name,0,0)
1799
1800class _FBGBag(ABag):
1801    def matches(self,frame,canv):
1802        fid = id(frame)
1803        return ((isinstance(self.fid,list) and fid in self.fid or fid==self.fid)
1804                    and id(canv)==self.cid and self.pn==canv.getPageNumber())
1805
1806    def getDims(self,canv):
1807        self._inst = canv._code[self.codePos].split()
1808        return map(float,self._inst[1:5])
1809
1810    def setDims(self,canv,x,y,w,h):
1811        self._inst[1:5] = [fp_str(x,y,w,h)]
1812        canv._code[self.codePos] = ' '.join(self._inst)
1813
1814    def render(self,canv,frame,fbx,fby,fbw,fbh):
1815        if abs(fbw)>_FUZZ and abs(fbh)>_FUZZ:
1816            pn = canv.getPageNumber()
1817            if self.fid==id(frame) and self.cid==id(canv) and self.pn==pn:
1818                ox,oy,ow,oh = self.getDims(canv)
1819                self.setDims(canv,ox,fby,ow,oh+oy-fby)
1820            else:
1821                canv.saveState()
1822                fbgc = self.fillColor
1823                if fbgc:
1824                    canv.setFillColor(fbgc)
1825                sw = self.strokeWidth
1826                sc = None if sw is None or sw<0 else self.strokeColor
1827                if sc:
1828                    canv.setStrokeColor(sc)
1829                    canv.setLineWidth(sw)
1830                    da = self.strokeDashArray
1831                    if da:
1832                        canv.setDash(da)
1833                self.fid = id(frame)
1834                self.cid = id(canv)
1835                self.pn = pn
1836                self.codePos = len(canv._code)
1837                canv.rect(fbx,fby,fbw,fbh,stroke=1 if sc else 0,fill=1 if fbgc else 0)
1838                canv.restoreState()
1839
1840class FrameBG(AnchorFlowable):
1841    """Start or stop coloring the frame background
1842    left & right are distances from the edge of the frame to start stop colouring.
1843    if start in ('frame','frame-permanent') then the background is filled from here to the bottom of the frame and immediately discarded
1844    for the frame case.
1845    """
1846    _ZEROSIZE=1
1847    def __init__(self, color=None, left=0, right=0, start=True, strokeWidth=None, strokeColor=None, strokeDashArray=None):
1848        Spacer.__init__(self,0,0)
1849        self.start = start
1850        if start:
1851            from reportlab.platypus.doctemplate import _evalMeasurement
1852            self.left = _evalMeasurement(left)
1853            self.right = _evalMeasurement(right)
1854            self.color = color
1855            self.strokeWidth = strokeWidth
1856            self.strokeColor = strokeColor
1857            self.strokeDashArray = strokeDashArray
1858
1859    def __repr__(self):
1860        return "%s(%s)" % (self.__class__.__name__,', '.join(['%s=%r' % (i,getattr(self,i,None)) for i in 'start color left right'.split()]))
1861
1862    def draw(self):
1863        frame = getattr(self,'_frame',None)
1864        if frame is None: return
1865        if self.start:
1866            sc = self.strokeColor
1867            sw = self.strokeWidth
1868            sw = -1 if sw is None else sw
1869            frame._frameBGs.append(
1870                        _FBGBag(left=self.left,
1871                                right=self.right,
1872                                fillColor=self.color,
1873                                start=self.start if self.start in ('frame','frame-permanent') else None,
1874                                strokeColor=self.strokeColor,
1875                                strokeWidth=self.strokeWidth,
1876                                strokeDashArray=self.strokeDashArray,
1877                                fid = 0,
1878                                cid = 0,
1879                                pn = -1,
1880                                codePos = None,
1881                                active=True,
1882                                ))
1883        elif frame._frameBGs:
1884            frame._frameBGs.pop()
1885
1886class FrameSplitter(NullDraw):
1887    '''When encountered this flowable should either switch directly to nextTemplate
1888    if remaining space in the current frame is less than gap+required or it should
1889    temporarily modify the current template to have the frames from nextTemplate
1890    that are listed in nextFrames and switch to the first of those frames.
1891    '''
1892    _ZEROSIZE=1
1893    def __init__(self, nextTemplate, nextFrames=[], gap=10, required=72, adjustHeight=True):
1894        self.nextTemplate = nextTemplate
1895        self.nextFrames = nextFrames or []
1896        self.gap = gap
1897        self.required = required
1898        self.adjustHeight = adjustHeight
1899
1900    def wrap(self,aW,aH):
1901        frame = self._frame
1902        from reportlab.platypus.doctemplate import NextPageTemplate,CurrentFrameFlowable,LayoutError
1903        G=[NextPageTemplate(self.nextTemplate)]
1904        if aH<self.gap+self.required-_FUZZ:
1905            #we are going straight to the nextTemplate with no attempt to modify the frames
1906            G.append(PageBreak())
1907        else:
1908            #we are going to modify the incoming templates
1909            templates = self._doctemplateAttr('pageTemplates')
1910            if templates is None:
1911                raise LayoutError('%s called in non-doctemplate environment'%self.identity())
1912            T=[t for t in templates if t.id==self.nextTemplate]
1913            if not T:
1914                raise LayoutError('%s.nextTemplate=%s not found' % (self.identity(),self.nextTemplate))
1915            T=T[0]
1916            F=[f for f in T.frames if f.id in self.nextFrames]
1917            N=[f.id for f in F]
1918            N=[f for f in self.nextFrames if f not in N]
1919            if N:
1920                raise LayoutError('%s frames=%r not found in pageTemplate(%s)\n%r has frames %r' % (self.identity(),N,T.id,T,[f.id for f in T.frames]))
1921            T=self._doctemplateAttr('pageTemplate')
1922            def unwrap(canv,doc,T=T,onPage=T.onPage,oldFrames=T.frames):
1923                T.frames=oldFrames
1924                T.onPage=onPage
1925                onPage(canv,doc)
1926            T.onPage=unwrap
1927            h=aH-self.gap
1928            for i,f in enumerate(F):
1929                f=copy(f)
1930                if self.adjustHeight: f.height=h
1931                f._reset()
1932                F[i]=f
1933            T.frames=F
1934            G.append(CurrentFrameFlowable(F[0].id))
1935        frame.add_generated_content(*G)
1936        return 0,0
1937
1938
1939from reportlab.lib.sequencer import _type2formatter
1940_bulletNames = dict(
1941                bulletchar=u'\u2022',   #usually a small circle
1942                circle=u'\u25cf',   #circle as high as the font
1943                square=u'\u25a0',
1944                disc=u'\u25cb',
1945                diamond=u'\u25c6',
1946                diamondwx=u'\u2756',
1947                rarrowhead=u'\u27a4',
1948                sparkle=u'\u2747',
1949                squarelrs=u'\u274f',
1950                blackstar=u'\u2605',
1951                )
1952
1953def _bulletFormat(value,type='1',format=None):
1954    if type=='bullet':
1955        s = _bulletNames.get(value,value)
1956    else:
1957        s = _type2formatter[type](int(value))
1958
1959    if format:
1960        if isinstance(format,strTypes):
1961            s = format % s
1962        elif callable(format):
1963            s = format(s)
1964        else:
1965            raise ValueError('unexpected BulletDrawer format %r' % format)
1966    return s
1967
1968class BulletDrawer:
1969    def __init__(self,
1970                    value='0',
1971                    bulletAlign='left',
1972                    bulletType='1',
1973                    bulletColor='black',
1974                    bulletFontName='Helvetica',
1975                    bulletFontSize=12,
1976                    bulletOffsetY=0,
1977                    bulletDedent=0,
1978                    bulletDir='ltr',
1979                    bulletFormat=None,
1980                    ):
1981        self.value = value
1982        self._bulletAlign = bulletAlign
1983        self._bulletType = bulletType
1984        self._bulletColor = bulletColor
1985        self._bulletFontName = bulletFontName
1986        self._bulletFontSize = bulletFontSize
1987        self._bulletOffsetY = bulletOffsetY
1988        self._bulletDedent = bulletDedent
1989        self._bulletDir = bulletDir
1990        self._bulletFormat = bulletFormat
1991
1992    def drawOn(self,indenter,canv,x,y,_sW=0):
1993        value = self.value
1994        if not value: return
1995        canv.saveState()
1996        canv.translate(x, y)
1997
1998        y = indenter.height-self._bulletFontSize+self._bulletOffsetY
1999        if self._bulletDir=='rtl':
2000            x = indenter.width - indenter._rightIndent + self._bulletDedent
2001        else:
2002            x = indenter._leftIndent - self._bulletDedent
2003        canv.setFont(self._bulletFontName,self._bulletFontSize)
2004        canv.setFillColor(self._bulletColor)
2005        bulletAlign = self._bulletAlign
2006        value = _bulletFormat(value,self._bulletType,self._bulletFormat)
2007
2008        if bulletAlign=='left':
2009            canv.drawString(x,y,value)
2010        elif bulletAlign=='right':
2011            canv.drawRightString(x,y,value)
2012        elif bulletAlign in ('center','centre'):
2013            canv.drawCentredString(x,y,value)
2014        elif bulletAlign.startswith('numeric') or bulletAlign.startswith('decimal'):
2015            pc = bulletAlign[7:].strip() or '.'
2016            canv.drawAlignedString(x,y,value,pc)
2017        else:
2018            raise ValueError('Invalid bulletAlign: %r' % bulletAlign)
2019        canv.restoreState()
2020
2021def _computeBulletWidth(b,value):
2022    value = _bulletFormat(value,b._bulletType,b._bulletFormat)
2023    return stringWidth(value,b._bulletFontName,b._bulletFontSize)
2024
2025class DDIndenter(Flowable):
2026    _IndenterAttrs = '_flowable _leftIndent _rightIndent width height'.split()
2027    def __init__(self,flowable,leftIndent=0,rightIndent=0):
2028        self._flowable = flowable
2029        self._leftIndent = leftIndent
2030        self._rightIndent = rightIndent
2031        self.width = None
2032        self.height = None
2033
2034    def split(self, aW, aH):
2035        S = self._flowable.split(aW-self._leftIndent-self._rightIndent, aH)
2036        return [
2037                DDIndenter(s,
2038                        leftIndent=self._leftIndent,
2039                        rightIndent=self._rightIndent,
2040                        ) for s in S
2041                ]
2042
2043    def drawOn(self, canv, x, y, _sW=0):
2044        self._flowable.drawOn(canv,x+self._leftIndent,y,max(0,_sW-self._leftIndent-self._rightIndent))
2045
2046    def wrap(self, aW, aH):
2047        w,h = self._flowable.wrap(aW-self._leftIndent-self._rightIndent, aH)
2048        self.width = w+self._leftIndent+self._rightIndent
2049        self.height = h
2050        return self.width,h
2051
2052    def __getattr__(self,a):
2053        if a in self._IndenterAttrs:
2054            try:
2055                return self.__dict__[a]
2056            except KeyError:
2057                if a not in ('spaceBefore','spaceAfter'):
2058                    raise AttributeError('%r has no attribute %s' % (self,a))
2059        return getattr(self._flowable,a)
2060
2061    def __setattr__(self,a,v):
2062        if a in self._IndenterAttrs:
2063            self.__dict__[a] = v
2064        else:
2065            setattr(self._flowable,a,v)
2066
2067    def __delattr__(self,a):
2068        if a in self._IndenterAttrs:
2069            del self.__dict__[a]
2070        else:
2071            delattr(self._flowable,a)
2072
2073    def identity(self,maxLen=None):
2074        return '%s containing %s' % (self.__class__.__name__,self._flowable.identity(maxLen))
2075
2076class LIIndenter(DDIndenter):
2077    _IndenterAttrs = '_flowable _bullet _leftIndent _rightIndent width height spaceBefore spaceAfter'.split()
2078    def __init__(self,flowable,leftIndent=0,rightIndent=0,bullet=None, spaceBefore=None, spaceAfter=None):
2079        self._flowable = flowable
2080        self._bullet = bullet
2081        self._leftIndent = leftIndent
2082        self._rightIndent = rightIndent
2083        self.width = None
2084        self.height = None
2085        if spaceBefore is not None:
2086            self.spaceBefore = spaceBefore
2087        if spaceAfter is not None:
2088            self.spaceAfter = spaceAfter
2089
2090    def split(self, aW, aH):
2091        S = self._flowable.split(aW-self._leftIndent-self._rightIndent, aH)
2092        return [
2093                LIIndenter(s,
2094                        leftIndent=self._leftIndent,
2095                        rightIndent=self._rightIndent,
2096                        bullet = (s is S[0] and self._bullet or None),
2097                        ) for s in S
2098                ]
2099
2100    def drawOn(self, canv, x, y, _sW=0):
2101        if self._bullet:
2102            self._bullet.drawOn(self,canv,x,y,0)
2103        self._flowable.drawOn(canv,x+self._leftIndent,y,max(0,_sW-self._leftIndent-self._rightIndent))
2104
2105
2106from reportlab.lib.styles import ListStyle
2107class ListItem:
2108    def __init__(self,
2109                    flowables,  #the initial flowables
2110                    style=None,
2111                    #leftIndent=18,
2112                    #rightIndent=0,
2113                    #spaceBefore=None,
2114                    #spaceAfter=None,
2115                    #bulletType='1',
2116                    #bulletColor='black',
2117                    #bulletFontName='Helvetica',
2118                    #bulletFontSize=12,
2119                    #bulletOffsetY=0,
2120                    #bulletDedent='auto',
2121                    #bulletDir='ltr',
2122                    #bulletFormat=None,
2123                    **kwds
2124                    ):
2125        if not isinstance(flowables,(list,tuple)):
2126            flowables = (flowables,)
2127        self._flowables = flowables
2128        params = self._params = {}
2129
2130        if style:
2131            if not isinstance(style,ListStyle):
2132                raise ValueError('%s style argument (%r) not a ListStyle' % (self.__class__.__name__,style))
2133            self._style = style
2134
2135        for k in ListStyle.defaults:
2136            if k in kwds:
2137                v = kwds.get(k)
2138            elif style:
2139                v = getattr(style,k)
2140            else:
2141                continue
2142            params[k] = v
2143
2144        for k in ('value', 'spaceBefore','spaceAfter'):
2145            v = kwds.get(k,getattr(style,k,None))
2146            if v is not None:
2147                params[k] = v
2148
2149class _LIParams:
2150    def __init__(self,flowable,params,value,first):
2151        self.flowable = flowable
2152        self.params = params
2153        self.value = value
2154        self.first= first
2155
2156class ListFlowable(_Container,Flowable):
2157    _numberStyles = '1aAiI'
2158    def __init__(self,
2159                    flowables,  #the initial flowables
2160                    start=None,
2161                    style=None,
2162                    #leftIndent=18,
2163                    #rightIndent=0,
2164                    #spaceBefore=None,
2165                    #spaceAfter=None,
2166                    #bulletType='1',
2167                    #bulletColor='black',
2168                    #bulletFontName='Helvetica',
2169                    #bulletFontSize=12,
2170                    #bulletOffsetY=0,
2171                    #bulletDedent='auto',
2172                    #bulletDir='ltr',
2173                    #bulletFormat=None,
2174                    **kwds
2175                    ):
2176        self._flowables = flowables
2177
2178        if style:
2179            if not isinstance(style,ListStyle):
2180                raise ValueError('%s style argument not a ListStyle' % self.__class__.__name__)
2181            self.style = style
2182
2183        for k,v in ListStyle.defaults.items():
2184            setattr(self,'_'+k,kwds.get(k,getattr(style,k,v)))
2185
2186        for k in ('spaceBefore','spaceAfter'):
2187            v = kwds.get(k,getattr(style,k,None))
2188            if v is not None:
2189                setattr(self,k,v)
2190
2191        auto = False
2192        if start is None:
2193            start = getattr(self,'_start',None)
2194            if start is None:
2195                if self._bulletType=='bullet':
2196                    start = 'bulletchar'
2197                    auto = True
2198                else:
2199                    start = self._bulletType
2200                    auto = True
2201        if self._bulletType!='bullet':
2202            if auto:
2203                for v in start:
2204                    if v not in self._numberStyles:
2205                        raise ValueError('invalid start=%r or bullettype=%r' % (start,self._bulletType))
2206            else:
2207                for v in self._bulletType:
2208                    if v not in self._numberStyles:
2209                        raise ValueError('invalid bullettype=%r' % self._bulletType)
2210        self._start = start
2211        self._auto = auto or isinstance(start,(list,tuple))
2212
2213        self._list_content = None
2214        self._dims = None
2215
2216    @property
2217    def _content(self):
2218        if self._list_content is None:
2219            self._list_content = self._getContent()
2220            del self._flowables
2221        return self._list_content
2222
2223    def wrap(self,aW,aH):
2224        if self._dims!=aW:
2225            self.width, self.height = _listWrapOn(self._content,aW,self.canv)
2226            self._dims = aW
2227        return self.width,self.height
2228
2229    def split(self,aW,aH):
2230        return self._content
2231
2232    def _flowablesIter(self):
2233        for f in self._flowables:
2234            if isinstance(f,(list,tuple)):
2235                if f:
2236                    for i, z in enumerate(f):
2237                        yield i==0 and not isinstance(z,LIIndenter), z
2238            elif isinstance(f,ListItem):
2239                params = f._params
2240                if not params:
2241                    #meerkat simples just a list like object
2242                    for i, z in enumerate(f._flowables):
2243                        if isinstance(z,LIIndenter):
2244                            raise ValueError('LIIndenter not allowed in ListItem')
2245                        yield i==0, z
2246                else:
2247                    params = params.copy()
2248                    value = params.pop('value',None)
2249                    spaceBefore = params.pop('spaceBefore',None)
2250                    spaceAfter = params.pop('spaceAfter',None)
2251                    n = len(f._flowables) - 1
2252                    for i, z in enumerate(f._flowables):
2253                        P = params.copy()
2254                        if not i and spaceBefore is not None:
2255                            P['spaceBefore'] = spaceBefore
2256                        if i==n and spaceAfter is not None:
2257                            P['spaceAfter'] = spaceAfter
2258                        if i: value=None
2259                        yield 0, _LIParams(z,P,value,i==0)
2260            else:
2261                yield not isinstance(f,LIIndenter), f
2262
2263    def _makeLIIndenter(self,flowable, bullet, params=None):
2264        if params:
2265            leftIndent = params.get('leftIndent',self._leftIndent)
2266            rightIndent = params.get('rightIndent',self._rightIndent)
2267            spaceBefore = params.get('spaceBefore',None)
2268            spaceAfter = params.get('spaceAfter',None)
2269            return LIIndenter(flowable,leftIndent,rightIndent,bullet,spaceBefore=spaceBefore,spaceAfter=spaceAfter)
2270        else:
2271            return LIIndenter(flowable,self._leftIndent,self._rightIndent,bullet)
2272
2273    def _makeBullet(self,value,params=None):
2274        if params is None:
2275            def getp(a):
2276                return getattr(self,'_'+a)
2277        else:
2278            style = getattr(params,'style',None)
2279            def getp(a):
2280                if a in params: return params[a]
2281                if style and a in style.__dict__: return getattr(self,a)
2282                return getattr(self,'_'+a)
2283
2284        return BulletDrawer(
2285                    value=value,
2286                    bulletAlign=getp('bulletAlign'),
2287                    bulletType=getp('bulletType'),
2288                    bulletColor=getp('bulletColor'),
2289                    bulletFontName=getp('bulletFontName'),
2290                    bulletFontSize=getp('bulletFontSize'),
2291                    bulletOffsetY=getp('bulletOffsetY'),
2292                    bulletDedent=getp('calcBulletDedent'),
2293                    bulletDir=getp('bulletDir'),
2294                    bulletFormat=getp('bulletFormat'),
2295                    )
2296
2297    def _getContent(self):
2298        bt = self._bulletType
2299        value = self._start
2300        if isinstance(value,(list,tuple)):
2301            values = value
2302            value = values[0]
2303        else:
2304            values = [value]
2305        autov = values[0]
2306        inc = int(bt in '1aAiI')
2307        if inc:
2308            try:
2309                value = int(value)
2310            except:
2311                value = 1
2312
2313        bd = self._bulletDedent
2314        if bd=='auto':
2315            align = self._bulletAlign
2316            dir = self._bulletDir
2317            if dir=='ltr' and align=='left':
2318                bd = self._leftIndent
2319            elif align=='right':
2320                bd = self._rightIndent
2321            else:
2322                #we need to work out the maximum width of any of the labels
2323                tvalue = value
2324                maxW = 0
2325                for d,f in self._flowablesIter():
2326                    if d:
2327                        maxW = max(maxW,_computeBulletWidth(self,tvalue))
2328                        if inc: tvalue += inc
2329                    elif isinstance(f,LIIndenter):
2330                        b = f._bullet
2331                        if b:
2332                            if b.bulletType==bt:
2333                                maxW = max(maxW,_computeBulletWidth(b,b.value))
2334                                tvalue = int(b.value)
2335                        else:
2336                            maxW = max(maxW,_computeBulletWidth(self,tvalue))
2337                        if inc: tvalue += inc
2338                if dir=='ltr':
2339                    if align=='right':
2340                        bd = self._leftIndent - maxW
2341                    else:
2342                        bd = self._leftIndent - maxW*0.5
2343                elif align=='left':
2344                    bd = self._rightIndent - maxW
2345                else:
2346                    bd = self._rightIndent - maxW*0.5
2347
2348        self._calcBulletDedent = bd
2349
2350        S = []
2351        aS = S.append
2352        i=0
2353        for d,f in self._flowablesIter():
2354            if isinstance(f,ListFlowable):
2355                fstart = f._start
2356                if isinstance(fstart,(list,tuple)):
2357                    fstart = fstart[0]
2358                if fstart in values:
2359                    #my kind of ListFlowable
2360                    if f._auto:
2361                        autov = values.index(autov)+1
2362                        f._start = values[autov:]+values[:autov]
2363                        autov = f._start[0]
2364                        if inc: f._bulletType = autov
2365                    else:
2366                        autov = fstart
2367            fparams = {}
2368            if not i:
2369                i += 1
2370                spaceBefore = getattr(self,'spaceBefore',None)
2371                if spaceBefore is not None:
2372                    fparams['spaceBefore'] = spaceBefore
2373            if d:
2374                aS(self._makeLIIndenter(f,bullet=self._makeBullet(value),params=fparams))
2375                if inc: value += inc
2376            elif isinstance(f,LIIndenter):
2377                b = f._bullet
2378                if b:
2379                    if b.bulletType!=bt:
2380                        raise ValueError('Included LIIndenter bulletType=%s != OrderedList bulletType=%s' % (b.bulletType,bt))
2381                    value = int(b.value)
2382                else:
2383                    f._bullet = self._makeBullet(value,params=getattr(f,'params',None))
2384                if fparams:
2385                    f.__dict__['spaceBefore'] = max(f.__dict__.get('spaceBefore',0),spaceBefore)
2386                aS(f)
2387                if inc: value += inc
2388            elif isinstance(f,_LIParams):
2389                fparams.update(f.params)
2390                z = self._makeLIIndenter(f.flowable,bullet=None,params=fparams)
2391                if f.first:
2392                    if f.value is not None:
2393                        value = f.value
2394                        if inc: value = int(value)
2395                    z._bullet = self._makeBullet(value,f.params)
2396                    if inc: value += inc
2397                aS(z)
2398            else:
2399                aS(self._makeLIIndenter(f,bullet=None,params=fparams))
2400
2401        spaceAfter = getattr(self,'spaceAfter',None)
2402        if spaceAfter is not None:
2403            f=S[-1]
2404            f.__dict__['spaceAfter'] = max(f.__dict__.get('spaceAfter',0),spaceAfter)
2405        return S
2406
2407class TopPadder(Flowable):
2408    '''wrap a single flowable so that its first bit will be
2409    padded to fill out the space so that it appears at the
2410    bottom of its frame'''
2411    def __init__(self,f):
2412        self.__dict__['_TopPadder__f'] = f
2413
2414    def wrap(self,aW,aH):
2415        w,h = self.__f.wrap(aW,aH)
2416        self.__dict__['_TopPadder__dh'] = aH-h
2417        return w,h
2418
2419    def split(self,aW,aH):
2420        S = self.__f.split(aW,aH)
2421        if len(S)>1:
2422            S[0] = TopPadder(S[0])
2423        return S
2424
2425    def drawOn(self, canvas, x, y, _sW=0):
2426        self.__f.drawOn(canvas,x,y-max(0,self.__dh-1e-8),_sW)
2427
2428    def __setattr__(self,a,v):
2429        setattr(self.__f,a,v)
2430
2431    def __getattr__(self,a):
2432        return getattr(self.__f,a)
2433
2434    def __delattr__(self,a):
2435        delattr(self.__f,a)
2436
2437class DocAssign(NullDraw):
2438    '''At wrap time this flowable evaluates var=expr in the doctemplate namespace'''
2439    _ZEROSIZE=1
2440    def __init__(self,var,expr,life='forever'):
2441        Flowable.__init__(self)
2442        self.args = var,expr,life
2443
2444    def funcWrap(self,aW,aH):
2445        NS=self._doctemplateAttr('_nameSpace')
2446        NS.update(dict(availableWidth=aW,availableHeight=aH))
2447        try:
2448            return self.func()
2449        finally:
2450            for k in 'availableWidth','availableHeight':
2451                try:
2452                    del NS[k]
2453                except:
2454                    pass
2455
2456    def func(self):
2457        return self._doctemplateAttr('d'+self.__class__.__name__[1:])(*self.args)
2458
2459    def wrap(self,aW,aH):
2460        self.funcWrap(aW,aH)
2461        return 0,0
2462
2463class DocExec(DocAssign):
2464    '''at wrap time exec stmt in doc._nameSpace'''
2465    def __init__(self,stmt,lifetime='forever'):
2466        Flowable.__init__(self)
2467        self.args=stmt,lifetime
2468
2469class DocPara(DocAssign):
2470    '''at wrap time create a paragraph with the value of expr as text
2471    if format is specified it should use %(__expr__)s for string interpolation
2472    of the expression expr (if any). It may also use %(name)s interpolations
2473    for other variables in the namespace.
2474    suitable defaults will be used if style and klass are None
2475    '''
2476    def __init__(self,expr,format=None,style=None,klass=None,escape=True):
2477        Flowable.__init__(self)
2478        self.expr=expr
2479        self.format=format
2480        self.style=style
2481        self.klass=klass
2482        self.escape=escape
2483
2484    def func(self):
2485        expr = self.expr
2486        if expr:
2487            if not isinstance(expr,str): expr = str(expr)
2488            return self._doctemplateAttr('docEval')(expr)
2489
2490    def add_content(self,*args):
2491        self._doctemplateAttr('frame').add_generated_content(*args)
2492
2493    def get_value(self,aW,aH):
2494        value = self.funcWrap(aW,aH)
2495        if self.format:
2496            NS=self._doctemplateAttr('_nameSpace').copy()
2497            NS.update(dict(availableWidth=aW,availableHeight=aH))
2498            NS['__expr__'] = value
2499            value = self.format % NS
2500        else:
2501            value = str(value)
2502        return value
2503
2504    def wrap(self,aW,aH):
2505        value = self.get_value(aW,aH)
2506        P = self.klass
2507        if not P:
2508            from reportlab.platypus.paragraph import Paragraph as P
2509        style = self.style
2510        if not style:
2511            from reportlab.lib.styles import getSampleStyleSheet
2512            style=getSampleStyleSheet()['Code']
2513        if self.escape:
2514            from xml.sax.saxutils import escape
2515            value=escape(value)
2516        self.add_content(P(value,style=style))
2517        return 0,0
2518
2519class DocAssert(DocPara):
2520    def __init__(self,cond,format=None):
2521        Flowable.__init__(self)
2522        self.expr=cond
2523        self.format=format
2524
2525    def funcWrap(self,aW,aH):
2526        self._cond = DocPara.funcWrap(self,aW,aH)
2527        return self._cond
2528
2529    def wrap(self,aW,aH):
2530        value = self.get_value(aW,aH)
2531        if not bool(self._cond):
2532            raise AssertionError(value)
2533        return 0,0
2534
2535class DocIf(DocPara):
2536    def __init__(self,cond,thenBlock,elseBlock=[]):
2537        Flowable.__init__(self)
2538        self.expr = cond
2539        self.blocks = elseBlock or [],thenBlock
2540
2541    def checkBlock(self,block):
2542        if not isinstance(block,(list,tuple)):
2543            block = (block,)
2544        return block
2545
2546    def wrap(self,aW,aH):
2547        self.add_content(*self.checkBlock(self.blocks[int(bool(self.funcWrap(aW,aH)))]))
2548        return 0,0
2549
2550class DocWhile(DocIf):
2551    def __init__(self,cond,whileBlock):
2552        Flowable.__init__(self)
2553        self.expr = cond
2554        self.block = self.checkBlock(whileBlock)
2555
2556    def wrap(self,aW,aH):
2557        if bool(self.funcWrap(aW,aH)):
2558            self.add_content(*(list(self.block)+[self]))
2559        return 0,0
2560
2561class SetTopFlowables(NullDraw):
2562    _ZEROZSIZE = 1
2563    def __init__(self,F,show=False):
2564        self._F = F
2565        self._show = show
2566
2567    def wrap(self,aW,aH):
2568        doc = getattr(getattr(self,'canv',None),'_doctemplate',None)
2569        if doc:
2570            doc._topFlowables = self._F
2571            if self._show and self._F:
2572                doc.frame._generated_content = self._F
2573        return 0,0
2574
2575class SetPageTopFlowables(NullDraw):
2576    _ZEROZSIZE = 1
2577    def __init__(self,F,show=False):
2578        self._F = F
2579        self._show = show
2580
2581    def wrap(self,aW,aH):
2582        doc = getattr(getattr(self,'canv',None),'_doctemplate',None)
2583        if doc:
2584            doc._pageTopFlowables = self._F
2585            if self._show and self._F:
2586                doc.frame._generated_content = self._F
2587        return 0,0
2588