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/tables.py
4__all__= (
5        'Table',
6        'TableStyle',
7        'CellStyle',
8        'LongTable',
9        )
10__version__='3.5.21'
11
12__doc__="""
13Tables are created by passing the constructor a tuple of column widths, a tuple of row heights and the data in
14row order. Drawing of the table can be controlled by using a TableStyle instance. This allows control of the
15color and weight of the lines (if any), and the font, alignment and padding of the text.
16
17None values in the sequence of row heights or column widths, mean that the corresponding rows
18or columns should be automatically sized.
19
20All the cell values should be convertible to strings; embedded newline '\\n' characters
21cause the value to wrap (ie are like a traditional linefeed).
22
23See the test output from running this module as a script for a discussion of the method for constructing
24tables and table styles.
25"""
26from reportlab.platypus.flowables import Flowable, Preformatted
27from reportlab import rl_config, xrange, ascii
28from reportlab.lib.styles import PropertySet, ParagraphStyle, _baseFontName
29from reportlab.lib import colors
30from reportlab.lib.utils import annotateException, IdentStr, flatten, isStr, asNative, strTypes, __UNSET__
31from reportlab.lib.validators import isListOfNumbersOrNone
32from reportlab.lib.rl_accel import fp_str
33from reportlab.lib.abag import ABag as CellFrame
34from reportlab.pdfbase.pdfmetrics import stringWidth
35from reportlab.platypus.doctemplate import Indenter, NullActionFlowable
36from reportlab.platypus.flowables import LIIndenter
37from collections import namedtuple
38
39LINECAPS={None: None, 'butt':0,'round':1,'projecting':2,'squared':2}
40LINEJOINS={None: None, 'miter':0, 'mitre':0, 'round':1,'bevel':2}
41
42class CellStyle(PropertySet):
43    fontname = _baseFontName
44    fontsize = 10
45    leading = 12
46    leftPadding = 6
47    rightPadding = 6
48    topPadding = 3
49    bottomPadding = 3
50    firstLineIndent = 0
51    color = 'black'
52    alignment = 'LEFT'
53    background = 'white'
54    valign = "BOTTOM"
55    href = None
56    destination = None
57    def __init__(self, name, parent=None):
58        self.name = name
59        if parent is not None:
60            parent.copy(self)
61    def copy(self, result=None):
62        if result is None:
63            result = CellStyle()
64        for name in dir(self):
65            setattr(result, name, getattr(self, name))
66        return result
67
68class TableStyle:
69    def __init__(self, cmds=None, parent=None, **kw):
70        #handle inheritance from parent first.
71        if parent:
72            # copy the parents list at construction time
73            pcmds = parent.getCommands()[:]
74            self._opts = parent._opts
75            for a in ('spaceBefore','spaceAfter'):
76                if hasattr(parent,a):
77                    setattr(self,a,getattr(parent,a))
78        else:
79            pcmds = []
80
81        self._cmds = pcmds + list(cmds or [])
82        self._opts={}
83        self._opts.update(kw)
84
85    def add(self, *cmd):
86        self._cmds.append(cmd)
87    def __repr__(self):
88        return "TableStyle(\n%s\n) # end TableStyle" % "  \n".join(map(repr, self._cmds))
89    def getCommands(self):
90        return self._cmds
91
92def _rowLen(x):
93    return not isinstance(x,(tuple,list)) and 1 or len(x)
94
95def _calc_pc(V,avail):
96    '''check list V for percentage or * values
97    1) absolute values go through unchanged
98    2) percentages are used as weights for unconsumed space
99    3) if no None values were seen '*' weights are
100    set equally with unclaimed space
101    otherwise * weights are assigned as None'''
102    R = []
103    r = R.append
104    I = []
105    i = I.append
106    J = []
107    j = J.append
108    s = avail
109    w = n = 0.
110    for v in V:
111        if isinstance(v,strTypes):
112            v = str(v).strip()
113            if not v:
114                v = None
115                n += 1
116            elif v.endswith('%'):
117                v = float(v[:-1])
118                w += v
119                i(len(R))
120            elif v=='*':
121                j(len(R))
122            else:
123                v = float(v)
124                s -= v
125        elif v is None:
126            n += 1
127        else:
128            s -= v
129        r(v)
130    s = max(0.,s)
131    f = s/max(100.,w)
132    for i in I:
133        R[i] *= f
134        s -= R[i]
135    s = max(0.,s)
136    m = len(J)
137    if m:
138        v =  n==0 and s/m or None
139        for j in J:
140            R[j] = v
141    return R
142
143def _calcBezierPoints(P, kind):
144    '''calculate all or half of a bezier curve
145    kind==0 all, 1=first half else second half'''
146    if kind==0:
147        return P
148    else:
149        Q0 = (0.5*(P[0][0]+P[1][0]),0.5*(P[0][1]+P[1][1]))
150        Q1 = (0.5*(P[1][0]+P[2][0]),0.5*(P[1][1]+P[2][1]))
151        Q2 = (0.5*(P[2][0]+P[3][0]),0.5*(P[2][1]+P[3][1]))
152        R0 = (0.5*(Q0[0]+Q1[0]),0.5*(Q0[1]+Q1[1]))
153        R1 = (0.5*(Q1[0]+Q2[0]),0.5*(Q1[1]+Q2[1]))
154        S0 = (0.5*(R0[0]+R1[0]),0.5*(R0[1]+R1[1]))
155        return [P[0],Q0,R0,S0] if kind==1 else [S0,R1,Q2,P[3]]
156
157def _quadrantDef(xpos, ypos, corner, r, kind=0, direction='left-right', m=0.4472):
158    t = m*r
159    if xpos=='right' and ypos=='bottom': #bottom right
160        xhi,ylo = corner
161        P = [(xhi - r, ylo),(xhi-t, ylo), (xhi, ylo + t), (xhi, ylo + r)]
162    elif xpos=='right' and ypos=='top': #top right
163        xhi,yhi = corner
164        P = [(xhi, yhi - r),(xhi, yhi - t), (xhi - t, yhi), (xhi - r, yhi)]
165    elif xpos=='left' and ypos=='top': #top left
166        xlo,yhi = corner
167        P = [(xlo + r, yhi),(xlo + t, yhi), (xlo, yhi - t), (xlo, yhi - r)]
168    elif xpos=='left' and ypos=='bottom': #bottom left
169        xlo,ylo = corner
170        P = [(xlo, ylo + r),(xlo, ylo + t), (xlo + t, ylo), (xlo + r, ylo)]
171    else:
172        raise ValueError('Unknown quadrant position %s' % repr((xpos,ypos)))
173    if direction=='left-right' and P[0][0]>P[-1][0] or direction=='bottom-top' and P[0][1]>P[-1][1]:
174        P.reverse()
175    P = _calcBezierPoints(P, kind)
176    return P
177
178def _hLine(canvLine, scp, ecp, y, hBlocks, FUZZ=rl_config._FUZZ):
179    '''
180    Draw horizontal lines; do not draw through regions specified in hBlocks
181    This also serves for vertical lines with a suitable canvLine
182    '''
183    if hBlocks: hBlocks = hBlocks.get(y,None)
184    if not hBlocks or scp>=hBlocks[-1][1]-FUZZ or ecp<=hBlocks[0][0]+FUZZ:
185        canvLine(scp,y,ecp,y)
186    else:
187        i = 0
188        n = len(hBlocks)
189        while scp<ecp-FUZZ and i<n:
190            x0, x1 = hBlocks[i]
191            if x1<=scp+FUZZ or x0>=ecp-FUZZ:
192                i += 1
193                continue
194            i0 = max(scp,x0)
195            i1 = min(ecp,x1)
196            if i0>scp: canvLine(scp,y,i0,y)
197            scp = i1
198        if scp<ecp-FUZZ: canvLine(scp,y,ecp,y)
199
200def _multiLine(scp,ecp,y,canvLine,ws,count):
201    offset = 0.5*(count-1)*ws
202    y += offset
203    for idx in xrange(count):
204        canvLine(scp, y, ecp, y)
205        y -= ws
206
207def _convert2int(value, map, low, high, name, cmd):
208    '''private converter tries map(value) low<=int(value)<=high or finally an error'''
209    try:
210        return map[value]
211    except KeyError:
212        try:
213            ivalue = int(value)
214            if low<=ivalue<=high: return ivalue
215        except:
216            pass
217    raise ValueError('Bad %s value %s in %s'%(name,value,ascii(cmd)))
218
219def _endswith(obj,s):
220    try:
221        return obj.endswith(s)
222    except:
223        return 0
224
225def spanFixDim(V0,V,spanCons,lim=None,FUZZ=rl_config._FUZZ):
226    #assign required space to variable rows equally to existing calculated values
227    M = {}
228    if not lim: lim = len(V0)   #in longtables the row calcs may be truncated
229
230    #we assign the largest spaces first hoping to get a smaller result
231    for v,(x0,x1) in reversed(sorted(((iv,ik) for ik,iv in spanCons.items()))):
232        if x0>=lim: continue
233        x1 += 1
234        t = sum([V[x]+M.get(x,0) for x in xrange(x0,x1)])
235        if t>=v-FUZZ: continue      #already good enough
236        X = [x for x in xrange(x0,x1) if V0[x] is None] #variable candidates
237        if not X: continue          #something wrong here mate
238        v -= t
239        v /= float(len(X))
240        for x in X:
241            M[x] = M.get(x,0)+v
242    for x,v in M.items():
243        V[x] += v
244
245class _ExpandedCellTuple(tuple):
246    pass
247
248
249RoundingRectDef = namedtuple('RoundingRectDefs','x0 y0 w h x1 y1 ar SL')
250RoundingRectLine = namedtuple('RoundingRectLine','xs ys xe ye weight color cap dash join')
251
252class Table(Flowable):
253    def __init__(self, data, colWidths=None, rowHeights=None, style=None,
254                repeatRows=0, repeatCols=0, splitByRow=1, emptyTableAction=None, ident=None,
255                hAlign=None,vAlign=None, normalizedData=0, cellStyles=None, rowSplitRange=None,
256                spaceBefore=None,spaceAfter=None, longTableOptimize=None, minRowHeights=None,
257                cornerRadii=__UNSET__, #or [topLeft, topRight, bottomLeft bottomRight]
258                ):
259        self.ident = ident
260        self.hAlign = hAlign or 'CENTER'
261        self.vAlign = vAlign or 'MIDDLE'
262        if not isinstance(data,(tuple,list)):
263            raise ValueError("%s invalid data type" % self.identity())
264        self._nrows = nrows = len(data)
265        self._cellvalues = []
266        _seqCW = isinstance(colWidths,(tuple,list))
267        _seqRH = isinstance(rowHeights,(tuple,list))
268        if nrows: self._ncols = ncols = max(list(map(_rowLen,data)))
269        elif colWidths and _seqCW: ncols = len(colWidths)
270        else: ncols = 0
271        if not emptyTableAction: emptyTableAction = rl_config.emptyTableAction
272        self._longTableOptimize = (getattr(self,'_longTableOptimize',rl_config.longTableOptimize)
273                                    if longTableOptimize is None else longTableOptimize)
274        if not (nrows and ncols):
275            if emptyTableAction=='error':
276                raise ValueError("%s must have at least a row and column" % self.identity())
277            elif emptyTableAction=='indicate':
278                self.__class__ = Preformatted
279                global _emptyTableStyle
280                if '_emptyTableStyle' not in list(globals().keys()):
281                    _emptyTableStyle = ParagraphStyle('_emptyTableStyle')
282                    _emptyTableStyle.textColor = colors.red
283                    _emptyTableStyle.backColor = colors.yellow
284                Preformatted.__init__(self,'%s(%d,%d)' % (self.__class__.__name__,nrows,ncols), _emptyTableStyle)
285            elif emptyTableAction=='ignore':
286                self.__class__ = NullActionFlowable
287            else:
288                raise ValueError('%s bad emptyTableAction: "%s"' % (self.identity(),emptyTableAction))
289            return
290
291        # we need a cleanup pass to ensure data is strings - non-unicode and non-null
292        if normalizedData:
293            self._cellvalues = data
294        else:
295            self._cellvalues = data = self.normalizeData(data)
296        if not _seqCW: colWidths = ncols*[colWidths]
297        elif len(colWidths)!=ncols:
298            if rl_config.allowShortTableRows and isinstance(colWidths,list):
299                n = len(colWidths)
300                if n<ncols:
301                    colWidths[n:] = (ncols-n)*[colWidths[-1]]
302                else:
303                    colWidths = colWidths[:ncols]
304            else:
305                raise ValueError("%s data error - %d columns in data but %d in column widths" % (self.identity(),ncols, len(colWidths)))
306        if not _seqRH: rowHeights = nrows*[rowHeights]
307        elif len(rowHeights) != nrows:
308            raise ValueError("%s data error - %d rows in data but %d in row heights" % (self.identity(),nrows, len(rowHeights)))
309        for i,d in enumerate(data):
310            n = len(d)
311            if n!=ncols:
312                if rl_config.allowShortTableRows and isinstance(d,list):
313                    d[n:] = (ncols-n)*['']
314                else:
315                    raise ValueError("%s expected %d not %d columns in row %d!" % (self.identity(),ncols,n,i))
316        self._rowHeights = self._argH = rowHeights
317        self._colWidths = self._argW = colWidths
318        if cellStyles is None:
319            cellrows = []
320            for i in xrange(nrows):
321                cellcols = []
322                for j in xrange(ncols):
323                    cellcols.append(CellStyle(repr((i,j))))
324                cellrows.append(cellcols)
325            self._cellStyles = cellrows
326        else:
327            self._cellStyles = cellStyles
328
329        self._bkgrndcmds = []
330        self._linecmds = []
331        self._spanCmds = []
332        self._nosplitCmds = []
333        self._srflcmds = []
334        # NB repeatRows can be a list or tuple eg (1,) repeats only the second row of a table
335        # or an integer eg 2 to repeat both rows 0 & 1
336        self.repeatRows = repeatRows
337        self.repeatCols = repeatCols
338        self.splitByRow = splitByRow
339
340        if style:
341            self.setStyle(style)
342
343        if cornerRadii is not __UNSET__:    #instance argument overrides
344            self._setCornerRadii(cornerRadii)
345
346        self._rowSplitRange = rowSplitRange
347        if spaceBefore is not None:
348            self.spaceBefore = spaceBefore
349        if spaceAfter is not None:
350            self.spaceAfter = spaceAfter
351
352        if minRowHeights != None:
353            lmrh = len(minRowHeights)
354            if not lmrh:
355                raise ValueError("%s Supplied mismatching minimum row heights of length %d" % (self.identity(),lmrh))
356            elif lmrh<nrows:
357                minRowHeights = minRowHeights+(nrows-lmrh)*minRowHeights.__class__((0,))
358        self._minRowHeights = minRowHeights
359
360
361    def __repr__(self):
362        "incomplete, but better than nothing"
363        r = getattr(self,'_rowHeights','[unknown]')
364        c = getattr(self,'_colWidths','[unknown]')
365        cv = getattr(self,'_cellvalues','[unknown]')
366        import pprint
367        cv = pprint.pformat(cv)
368        cv = cv.replace("\n", "\n  ")
369        return "%s(\n rowHeights=%s,\n colWidths=%s,\n%s\n) # end table" % (self.__class__.__name__,r,c,cv)
370
371    def normalizeData(self, data):
372        """Takes a block of input data (list of lists etc.) and
373        - coerces unicode strings to non-unicode UTF8
374        - coerces nulls to ''
375        -
376
377        """
378        def normCell(stuff):
379            if stuff is None:
380                return ''
381            elif isStr(stuff):
382                return asNative(stuff)
383            else:
384                return stuff
385        outData = []
386        for row in data:
387            outRow = [normCell(cell) for cell in row]
388            outData.append(outRow)
389        return outData
390
391    def identity(self, maxLen=30):
392        '''Identify our selves as well as possible'''
393        if self.ident: return self.ident
394        vx = None
395        nr = getattr(self,'_nrows','unknown')
396        nc = getattr(self,'_ncols','unknown')
397        cv = getattr(self,'_cellvalues',None)
398        rh = getattr(self, '_rowHeights', None)
399        if cv and 'unknown' not in (nr,nc):
400            b = 0
401            for i in xrange(nr):
402                for j in xrange(nc):
403                    v = cv[i][j]
404                    if isinstance(v,(list,tuple,Flowable)):
405                        if not isinstance(v,(tuple,list)): v = (v,)
406                        r = ''
407                        for vij in v:
408                            r = vij.identity(maxLen)
409                            if r and r[-4:]!='>...':
410                                break
411                        if r and r[-4:]!='>...':
412                            ix, jx, vx, b = i, j, r, 1
413                    else:
414                        v = v is None and '' or str(v)
415                        ix, jx, vx = i, j, v
416                        b = (vx and isinstance(v,strTypes)) and 1 or 0
417                        if maxLen: vx = vx[:maxLen]
418                    if b: break
419                if b: break
420        if rh:  #find tallest row, it's of great interest'
421            tallest = '(tallest row %d)' % int(max(rh))
422        else:
423            tallest = ''
424        if vx:
425            vx = ' with cell(%d,%d) containing\n%s' % (ix,jx,repr(vx))
426        else:
427            vx = '...'
428
429        return "<%s@0x%8.8X %s rows x %s cols%s>%s" % (self.__class__.__name__, id(self), nr, nc, tallest, vx)
430
431    def _cellListIter(self,C,aW,aH):
432        canv = getattr(self,'canv',None)
433        for c in C:
434            if getattr(c,'__split_only__',None):
435                for d in c.splitOn(canv,aW,aH):
436                    yield d
437            else:
438                yield c
439
440    def _cellListProcess(self,C,aW,aH):
441        if not isinstance(C,_ExpandedCellTuple):
442            frame = None
443            R = [].append
444            for c in self._cellListIter(C,aW,aH):
445                if isinstance(c,Indenter):
446                    if not frame:
447                        frame = CellFrame(_leftExtraIndent=0,_rightExtraIndent=0)
448                    c.frameAction(frame)
449                    if frame._leftExtraIndent<1e-8 and frame._rightExtraIndent<1e-8:
450                        frame = None
451                    continue
452                if frame:
453                    R(LIIndenter(c,leftIndent=frame._leftExtraIndent,rightIndent=frame._rightExtraIndent))
454                else:
455                    R(c)
456            C = _ExpandedCellTuple(R.__self__)
457        return C
458
459    def _listCellGeom(self, V,w,s,W=None,H=None,aH=72000):
460        if not V: return 0,0
461        aW = w - s.leftPadding - s.rightPadding
462        aH = aH - s.topPadding - s.bottomPadding
463        t = 0
464        w = 0
465        canv = getattr(self,'canv',None)
466        sb0 = None
467        for v in V:
468            vw, vh = v.wrapOn(canv, aW, aH)
469            sb = v.getSpaceBefore()
470            sa = v.getSpaceAfter()
471            if W is not None: W.append(vw)
472            if H is not None: H.append(vh)
473            w = max(w,vw)
474            t += vh + sa + sb
475            if sb0 is None:
476                sb0 = sb
477        return w, t - sb0 - sa
478
479    def _listValueWidth(self,V,aH=72000,aW=72000):
480        if not V: return 0,0
481        t = 0
482        w = 0
483        canv = getattr(self,'canv',None)
484        return max([v.wrapOn(canv,aW,aH)[0] for v in V])
485
486    def _calc_width(self,availWidth,W=None):
487        if getattr(self,'_width_calculated_once',None): return
488        #comments added by Andy to Robin's slightly terse variable names
489        if not W: W = _calc_pc(self._argW,availWidth)   #widths array
490        if None in W:  #some column widths are not given
491            canv = getattr(self,'canv',None)
492            saved = None
493            if self._spanCmds:
494                colSpanCells = self._colSpanCells
495                spanRanges = self._spanRanges
496            else:
497                colSpanCells = ()
498                spanRanges = {}
499            spanCons = {}
500            if W is self._argW:
501                W0 = W
502                W = W[:]
503            else:
504                W0 = W[:]
505            V = self._cellvalues
506            S = self._cellStyles
507            while None in W:
508                j = W.index(None) #find first unspecified column
509                w = 0
510                for i,Vi in enumerate(V):
511                    v = Vi[j]
512                    s = S[i][j]
513                    ji = j,i
514                    span = spanRanges.get(ji,None)
515                    if ji in colSpanCells and not span: #if the current cell is part of a spanned region,
516                        t = 0.0                         #assume a zero size.
517                    else:#work out size
518                        t = self._elementWidth(v,s)
519                        if t is None:
520                            raise ValueError("Flowable %s in cell(%d,%d) can't have auto width\n%s" % (v.identity(30),i,j,self.identity(30)))
521                        t += s.leftPadding+s.rightPadding
522                        if span:
523                            c0 = span[0]
524                            c1 = span[2]
525                            if c0!=c1:
526                                x = c0,c1
527                                spanCons[x] = max(spanCons.get(x,t),t)
528                                t = 0
529                    if t>w: w = t   #record a new maximum
530
531                W[j] = w
532
533            if spanCons:
534                try:
535                    spanFixDim(W0,W,spanCons)
536                except:
537                    annotateException('\nspanning problem in %s\nW0=%r W=%r\nspanCons=%r' % (self.identity(),W0,W,spanCons))
538
539        self._colWidths = W
540        width = 0
541        self._colpositions = [0]        #index -1 is right side boundary; we skip when processing cells
542        for w in W:
543            width = width + w
544            self._colpositions.append(width)
545
546        self._width = width
547        self._width_calculated_once = 1
548
549    def _elementWidth(self,v,s):
550        if isinstance(v,(list,tuple)):
551            w = 0
552            for e in v:
553                ew = self._elementWidth(e,s)
554                if ew is None: return None
555                w = max(w,ew)
556            return w
557        elif isinstance(v,Flowable):
558            if v._fixedWidth:
559                if hasattr(v, 'width') and isinstance(v.width,(int,float)): return v.width
560                if hasattr(v, 'drawWidth') and isinstance(v.drawWidth,(int,float)): return v.drawWidth
561            if hasattr(v,'__styledWrap__'): #very experimental
562                try:
563                    return getattr(v,'__styledWrap__')(s)[0]
564                except:
565                    pass
566        # Even if something is fixedWidth, the attribute to check is not
567        # necessarily consistent (cf. Image.drawWidth).  Therefore, we'll
568        # be extra-careful and fall through to this code if necessary.
569        if hasattr(v, 'minWidth'):
570            try:
571                w = v.minWidth() # should be all flowables
572                if isinstance(w,(float,int)): return w
573            except AttributeError:
574                pass
575        if v is None:
576            return 0
577        else:
578            try:
579                v = str(v).split("\n")
580            except:
581                return 0
582        fontName = s.fontname
583        fontSize = s.fontsize
584        return max([stringWidth(x,fontName,fontSize) for x in v])
585
586    def _calc_height(self, availHeight, availWidth, H=None, W=None):
587        H = self._argH
588        if not W: W = _calc_pc(self._argW,availWidth)   #widths array
589
590        hmax = lim = len(H)
591        longTable = self._longTableOptimize
592
593        if None in H:
594            minRowHeights = self._minRowHeights
595            canv = getattr(self,'canv',None)
596            saved = None
597            #get a handy list of any cells which span rows. should be ignored for sizing
598            if self._spanCmds:
599                rowSpanCells = self._rowSpanCells
600                colSpanCells = self._colSpanCells
601                spanRanges = self._spanRanges
602                colpositions = self._colpositions
603            else:
604                rowSpanCells = colSpanCells = ()
605                spanRanges = {}
606            if canv: saved = canv._fontname, canv._fontsize, canv._leading
607            H0 = H
608            H = H[:]    #make a copy as we'll change it
609            self._rowHeights = H
610            spanCons = {}
611            FUZZ = rl_config._FUZZ
612            while None in H:
613                i = H.index(None)
614                V = self._cellvalues[i] # values for row i
615                S = self._cellStyles[i] # styles for row i
616                h = 0
617                j = 0
618                for j,(v, s, w) in enumerate(list(zip(V, S, W))): # value, style, width (lengths must match)
619                    ji = j,i
620                    span = spanRanges.get(ji,None)
621                    if ji in rowSpanCells and not span:
622                        continue # don't count it, it's either occluded or unreliable
623                    else:
624                        if isinstance(v,(tuple,list,Flowable)):
625                            if isinstance(v,Flowable): v = (v,)
626                            else: v = flatten(v)
627                            v = V[j] = self._cellListProcess(v,w,None)
628                            if w is None and not self._canGetWidth(v):
629                                raise ValueError("Flowable %s in cell(%d,%d) can't have auto width in\n%s" % (v[0].identity(30),i,j,self.identity(30)))
630                            if canv: canv._fontname, canv._fontsize, canv._leading = s.fontname, s.fontsize, s.leading or 1.2*s.fontsize
631                            if ji in colSpanCells:
632                                if not span: continue
633                                w = max(colpositions[span[2]+1]-colpositions[span[0]],w or 0)
634                            dW,t = self._listCellGeom(v,w or self._listValueWidth(v),s)
635                            if canv: canv._fontname, canv._fontsize, canv._leading = saved
636                            dW = dW + s.leftPadding + s.rightPadding
637                            if not rl_config.allowTableBoundsErrors and dW>w:
638                                from reportlab.platypus.doctemplate import LayoutError
639                                raise LayoutError("Flowable %s (%sx%s points) too wide for cell(%d,%d) (%sx* points) in\n%s" % (v[0].identity(30),fp_str(dW),fp_str(t),i,j, fp_str(w), self.identity(30)))
640                        else:
641                            v = (v is not None and str(v) or '').split("\n")
642                            t = (s.leading or 1.2*s.fontsize)*len(v)
643                        t += s.bottomPadding+s.topPadding
644                        if span:
645                            r0 = span[1]
646                            r1 = span[3]
647                            if r0!=r1:
648                                x = r0,r1
649                                spanCons[x] = max(spanCons.get(x,t),t)
650                                t = 0
651                    if t>h: h = t   #record a new maximum
652                # If a minimum height has been specified use that, otherwise allow the cell to grow
653                H[i] = max(minRowHeights[i],h) if minRowHeights else h
654                # we can stop if we have filled up all available room
655                if longTable:
656                    hmax = i+1      #we computed H[i] so known len == i+1
657                    height = sum(H[:hmax])
658                    if height > availHeight:
659                        #we can terminate if all spans are complete in H[:hmax]
660                        if spanCons:
661                            msr = max(x[1] for x in spanCons.keys())    #RS=[endrowspan,.....]
662                            if hmax>msr:
663                                break
664            if None not in H: hmax = lim
665
666            if spanCons:
667                try:
668                    spanFixDim(H0,H,spanCons)
669                except:
670                    annotateException('\nspanning problem in %s hmax=%s lim=%s avail=%s x %s\nH0=%r H=%r\nspanCons=%r' % (self.identity(),hmax,lim,availWidth,availHeight,H0,H,spanCons))
671
672        #iterate backwards through the heights to get rowpositions in reversed order
673        self._rowpositions = j = []
674        height = c = 0
675        for i in xrange(hmax-1,-1,-1):
676            j.append(height)
677            y = H[i] - c
678            t = height + y
679            c = (t - height) - y
680            height = t
681        j.append(height)
682        self._height = height
683        j.reverse()     #reverse the reversed list of row positions
684        self._hmax = hmax
685
686    def _calc(self, availWidth, availHeight):
687        #if hasattr(self,'_width'): return
688
689        #in some cases there are unsizable things in
690        #cells.  If so, apply a different algorithm
691        #and assign some withs in a less (thanks to Gary Poster) dumb way.
692        #this CHANGES the widths array.
693        if (None in self._colWidths or '*' in self._colWidths) and self._hasVariWidthElements():
694            W = self._calcPreliminaryWidths(availWidth) #widths
695        else:
696            W = None
697
698        # need to know which cells are part of spanned
699        # ranges, so _calc_height and _calc_width can ignore them
700        # in sizing
701        if self._spanCmds:
702            self._calcSpanRanges()
703            if None in self._argH:
704                self._calc_width(availWidth,W=W)
705
706        if self._nosplitCmds:
707            self._calcNoSplitRanges()
708
709        # calculate the full table height
710        self._calc_height(availHeight,availWidth,W=W)
711
712        # calculate the full table width
713        self._calc_width(availWidth,W=W)
714
715        if self._spanCmds:
716            #now work out the actual rect for each spanned cell from the underlying grid
717            self._calcSpanRects()
718
719    def _culprit(self):
720        """Return a string describing the tallest element.
721
722        Usually this is what causes tables to fail to split.  Currently
723        tables are the only items to have a '_culprit' method. Doctemplate
724        checks for it.
725        """
726        rh = self._rowHeights
727        tallest = max(rh)
728        rowNum = rh.index(tallest)
729        #rowNum of limited interest as usually it's a split one
730        #and we see row #1.  Text might be a nice addition.
731
732        return 'tallest cell %0.1f points' % tallest
733
734
735
736    def _hasVariWidthElements(self, upToRow=None):
737        """Check for flowables in table cells and warn up front.
738
739        Allow a couple which we know are fixed size such as
740        images and graphics."""
741        if upToRow is None: upToRow = self._nrows
742        for row in xrange(min(self._nrows, upToRow)):
743            for col in xrange(self._ncols):
744                value = self._cellvalues[row][col]
745                if not self._canGetWidth(value):
746                    return 1
747        return 0
748
749    def _canGetWidth(self, thing):
750        "Can we work out the width quickly?"
751        if isinstance(thing,(list, tuple)):
752            for elem in thing:
753                if not self._canGetWidth(elem):
754                    return 0
755            return 1
756        elif isinstance(thing, Flowable):
757            return thing._fixedWidth  # must loosen this up
758        else: #str, number, None etc.
759            #anything else gets passed to str(...)
760            # so should be sizable
761            return 1
762
763    def _calcPreliminaryWidths(self, availWidth):
764        """Fallback algorithm for when main one fails.
765
766        Where exact width info not given but things like
767        paragraphs might be present, do a preliminary scan
768        and assign some best-guess values."""
769
770        W = list(self._argW) # _calc_pc(self._argW,availWidth)
771        #verbose = 1
772        totalDefined = 0.0
773        percentDefined = 0
774        percentTotal = 0
775        numberUndefined = 0
776        numberGreedyUndefined = 0
777        for w in W:
778            if w is None:
779                numberUndefined += 1
780            elif w == '*':
781                numberUndefined += 1
782                numberGreedyUndefined += 1
783            elif _endswith(w,'%'):
784                percentDefined += 1
785                percentTotal += float(w[:-1])
786            else:
787                assert isinstance(w,(int,float))
788                totalDefined = totalDefined + w
789        #if verbose: print('prelim width calculation.  %d columns, %d undefined width, %0.2f units remain' % (self._ncols, numberUndefined, availWidth - totalDefined))
790
791        #check columnwise in each None column to see if they are sizable.
792        given = []
793        sizeable = []
794        unsizeable = []
795        minimums = {}
796        totalMinimum = 0
797        elementWidth = self._elementWidth
798        for colNo in xrange(self._ncols):
799            w = W[colNo]
800            if w is None or w=='*' or _endswith(w,'%'):
801                siz = 1
802                final = 0
803                for rowNo in xrange(self._nrows):
804                    value = self._cellvalues[rowNo][colNo]
805                    style = self._cellStyles[rowNo][colNo]
806                    new = elementWidth(value,style) or 0
807                    new += style.leftPadding+style.rightPadding
808                    #if verbose: print('[%d,%d] new=%r-->%r' % (rowNo,colNo,new - style.leftPadding+style.rightPadding, new))
809                    final = max(final, new)
810                    siz = siz and self._canGetWidth(value) # irrelevant now?
811                if siz:
812                    sizeable.append(colNo)
813                else:
814                    unsizeable.append(colNo)
815                minimums[colNo] = final
816                totalMinimum += final
817            else:
818                given.append(colNo)
819        if len(given) == self._ncols:
820            return
821        #if verbose: print('predefined width:   ',given)
822        #if verbose: print('uncomputable width: ',unsizeable)
823        #if verbose: print('computable width:   ',sizeable)
824        #if verbose: print('minimums=%r' % (list(sorted(list(minimums.items()))),))
825
826        # how much width is left:
827        remaining = availWidth - (totalMinimum + totalDefined)
828        if remaining > 0:
829            # we have some room left; fill it.
830            definedPercentage = (totalDefined/float(availWidth))*100
831            percentTotal += definedPercentage
832            if numberUndefined and percentTotal < 100:
833                undefined = numberGreedyUndefined or numberUndefined
834                defaultWeight = (100-percentTotal)/float(undefined)
835                percentTotal = 100
836                defaultDesired = (defaultWeight/float(percentTotal))*availWidth
837            else:
838                defaultWeight = defaultDesired = 1
839            # we now calculate how wide each column wanted to be, and then
840            # proportionately shrink that down to fit the remaining available
841            # space.  A column may not shrink less than its minimum width,
842            # however, which makes this a bit more complicated.
843            desiredWidths = []
844            totalDesired = 0
845            effectiveRemaining = remaining
846            for colNo, minimum in minimums.items():
847                w = W[colNo]
848                if _endswith(w,'%'):
849                    desired = (float(w[:-1])/percentTotal)*availWidth
850                elif w == '*':
851                    desired = defaultDesired
852                else:
853                    desired = not numberGreedyUndefined and defaultDesired or 1
854                if desired <= minimum:
855                    W[colNo] = minimum
856                else:
857                    desiredWidths.append(
858                        (desired-minimum, minimum, desired, colNo))
859                    totalDesired += desired
860                    effectiveRemaining += minimum
861            if desiredWidths: # else we're done
862                # let's say we have two variable columns.  One wanted
863                # 88 points, and one wanted 264 points.  The first has a
864                # minWidth of 66, and the second of 55.  We have 71 points
865                # to divide up in addition to the totalMinimum (i.e.,
866                # remaining==71).  Our algorithm tries to keep the proportion
867                # of these variable columns.
868                #
869                # To do this, we add up the minimum widths of the variable
870                # columns and the remaining width.  That's 192.  We add up the
871                # totalDesired width.  That's 352.  That means we'll try to
872                # shrink the widths by a proportion of 192/352--.545454.
873                # That would make the first column 48 points, and the second
874                # 144 points--adding up to the desired 192.
875                #
876                # Unfortunately, that's too small for the first column.  It
877                # must be 66 points.  Therefore, we go ahead and save that
878                # column width as 88 points.  That leaves (192-88==) 104
879                # points remaining.  The proportion to shrink the remaining
880                # column is (104/264), which, multiplied  by the desired
881                # width of 264, is 104: the amount assigned to the remaining
882                # column.
883                proportion = effectiveRemaining/float(totalDesired)
884                # we sort the desired widths by difference between desired and
885                # and minimum values, a value called "disappointment" in the
886                # code.  This means that the columns with a bigger
887                # disappointment will have a better chance of getting more of
888                # the available space.
889                desiredWidths.sort()
890                finalSet = []
891                for disappointment, minimum, desired, colNo in desiredWidths:
892                    adjusted = proportion * desired
893                    if adjusted < minimum:
894                        W[colNo] = minimum
895                        totalDesired -= desired
896                        effectiveRemaining -= minimum
897                        if totalDesired:
898                            proportion = effectiveRemaining/float(totalDesired)
899                    else:
900                        finalSet.append((minimum, desired, colNo))
901                for minimum, desired, colNo in finalSet:
902                    adjusted = proportion * desired
903                    assert adjusted >= minimum
904                    W[colNo] = adjusted
905        else:
906            for colNo, minimum in minimums.items():
907                W[colNo] = minimum
908        #if verbose: print('new widths are:', W)
909        self._argW = self._colWidths = W
910        return W
911
912    def minWidth(self):
913        W = list(self._argW)
914        width = 0
915        elementWidth = self._elementWidth
916        rowNos = xrange(self._nrows)
917        values = self._cellvalues
918        styles = self._cellStyles
919        for colNo in xrange(len(W)):
920            w = W[colNo]
921            if w is None or w=='*' or _endswith(w,'%'):
922                final = 0
923                for rowNo in rowNos:
924                    value = values[rowNo][colNo]
925                    style = styles[rowNo][colNo]
926                    new = (elementWidth(value,style)+
927                           style.leftPadding+style.rightPadding)
928                    final = max(final, new)
929                width += final
930            else:
931                width += float(w)
932        return width # XXX + 1/2*(left and right border widths)
933
934    def _calcSpanRanges(self):
935        """Work out rects for tables which do row and column spanning.
936
937        This creates some mappings to let the later code determine
938        if a cell is part of a "spanned" range.
939        self._spanRanges shows the 'coords' in integers of each
940        'cell range', or None if it was clobbered:
941        (col, row) -> (col0, row0, col1, row1)
942
943        Any cell not in the key is not part of a spanned region
944        """
945        self._spanRanges = spanRanges = {}
946        for x in xrange(self._ncols):
947            for y in xrange(self._nrows):
948                spanRanges[x,y] = (x, y, x, y)
949        self._colSpanCells = []
950        self._rowSpanCells = []
951        csa = self._colSpanCells.append
952        rsa = self._rowSpanCells.append
953        for (cmd, start, stop) in self._spanCmds:
954            x0, y0 = start
955            x1, y1 = stop
956
957            #normalize
958            if x0 < 0: x0 = x0 + self._ncols
959            if x1 < 0: x1 = x1 + self._ncols
960            if y0 < 0: y0 = y0 + self._nrows
961            if y1 < 0: y1 = y1 + self._nrows
962            if x0 > x1: x0, x1 = x1, x0
963            if y0 > y1: y0, y1 = y1, y0
964
965            if x0!=x1 or y0!=y1:
966                if x0!=x1: #column span
967                    for y in xrange(y0, y1+1):
968                        for x in xrange(x0,x1+1):
969                            csa((x,y))
970                if y0!=y1: #row span
971                    for y in xrange(y0, y1+1):
972                        for x in xrange(x0,x1+1):
973                            rsa((x,y))
974
975                for y in xrange(y0, y1+1):
976                    for x in xrange(x0,x1+1):
977                        spanRanges[x,y] = None
978                # set the main entry
979                spanRanges[x0,y0] = (x0, y0, x1, y1)
980
981    def _calcNoSplitRanges(self):
982        """
983        This creates some mappings to let the later code determine
984        if a cell is part of a "nosplit" range.
985        self._nosplitRanges shows the 'coords' in integers of each
986        'cell range', or None if it was clobbered:
987        (col, row) -> (col0, row0, col1, row1)
988
989        Any cell not in the key is not part of a spanned region
990        """
991        self._nosplitRanges = nosplitRanges = {}
992        for x in xrange(self._ncols):
993            for y in xrange(self._nrows):
994                nosplitRanges[x,y] = (x, y, x, y)
995        self._colNoSplitCells = []
996        self._rowNoSplitCells = []
997        csa = self._colNoSplitCells.append
998        rsa = self._rowNoSplitCells.append
999        for (cmd, start, stop) in self._nosplitCmds:
1000            x0, y0 = start
1001            x1, y1 = stop
1002
1003            #normalize
1004            if x0 < 0: x0 = x0 + self._ncols
1005            if x1 < 0: x1 = x1 + self._ncols
1006            if y0 < 0: y0 = y0 + self._nrows
1007            if y1 < 0: y1 = y1 + self._nrows
1008            if x0 > x1: x0, x1 = x1, x0
1009            if y0 > y1: y0, y1 = y1, y0
1010
1011            if x0!=x1 or y0!=y1:
1012                #column span
1013                if x0!=x1:
1014                    for y in xrange(y0, y1+1):
1015                        for x in xrange(x0,x1+1):
1016                            csa((x,y))
1017                #row span
1018                if y0!=y1:
1019                    for y in xrange(y0, y1+1):
1020                        for x in xrange(x0,x1+1):
1021                            rsa((x,y))
1022
1023                for y in xrange(y0, y1+1):
1024                    for x in xrange(x0,x1+1):
1025                        nosplitRanges[x,y] = None
1026                # set the main entry
1027                nosplitRanges[x0,y0] = (x0, y0, x1, y1)
1028
1029    def _calcSpanRects(self):
1030        """Work out rects for tables which do row and column spanning.
1031
1032        Based on self._spanRanges, which is already known,
1033        and the widths which were given or previously calculated,
1034        self._spanRects shows the real coords for drawing:
1035
1036            (col, row) -> (x, y, width, height)
1037
1038        for each cell.  Any cell which 'does not exist' as another
1039        has spanned over it will get a None entry on the right
1040        """
1041        spanRects = getattr(self,'_spanRects',{})
1042        hmax = getattr(self,'_hmax',None)
1043        longTable = self._longTableOptimize
1044        if spanRects and (longTable and hmax==self._hmax_spanRects or not longTable):
1045            return
1046        colpositions = self._colpositions
1047        rowpositions = self._rowpositions
1048        vBlocks = {}
1049        hBlocks = {}
1050        rlim = len(rowpositions)-1
1051        for (coord, value) in self._spanRanges.items():
1052            if value is None:
1053                spanRects[coord] = None
1054            else:
1055                try:
1056                    col0, row0, col1, row1 = value
1057                    if row1>=rlim: continue
1058                    col,row = coord
1059                    if col1-col0>0:
1060                        for _ in xrange(col0+1,col1+1):
1061                            vBlocks.setdefault(colpositions[_],[]).append((rowpositions[row1+1],rowpositions[row0]))
1062                    if row1-row0>0:
1063                        for _ in xrange(row0+1,row1+1):
1064                            hBlocks.setdefault(rowpositions[_],[]).append((colpositions[col0],colpositions[col1+1]))
1065                    x = colpositions[col0]
1066                    y = rowpositions[row1+1]
1067                    width = colpositions[col1+1] - x
1068                    height = rowpositions[row0] - y
1069                    spanRects[coord] = (x, y, width, height)
1070                except:
1071                    annotateException('\nspanning problem in %s' % (self.identity(),))
1072
1073        for _ in hBlocks, vBlocks:
1074            for value in _.values():
1075                value.sort()
1076        self._spanRects = spanRects
1077        self._vBlocks = vBlocks
1078        self._hBlocks = hBlocks
1079        self._hmax_spanRects = hmax
1080
1081    def setStyle(self, tblstyle):
1082        if not isinstance(tblstyle,TableStyle):
1083            tblstyle = TableStyle(tblstyle)
1084        for cmd in tblstyle.getCommands():
1085            self._addCommand(cmd)
1086        for k,v in tblstyle._opts.items():
1087            setattr(self,k,v)
1088        for a in ('spaceBefore','spaceAfter'):
1089            if not hasattr(self,a) and hasattr(tblstyle,a):
1090                setattr(self,a,getattr(tblstyle,a))
1091
1092    def normCellRange(self, sc, ec, sr, er):
1093        '''ensure cell range ends are with the table bounds'''
1094        if sc < 0: sc = sc + self._ncols
1095        if ec < 0: ec = ec + self._ncols
1096        if sr < 0: sr = sr + self._nrows
1097        if er < 0: er = er + self._nrows
1098        return max(0,sc), min(self._ncols-1,ec), max(0,sr), min(self._nrows-1,er)
1099
1100    def _addCommand(self,cmd):
1101        if cmd[0] in ('BACKGROUND','ROWBACKGROUNDS','COLBACKGROUNDS'):
1102            self._bkgrndcmds.append(cmd)
1103        elif cmd[0] == 'SPAN':
1104            self._spanCmds.append(cmd)
1105        elif cmd[0] == 'NOSPLIT':
1106            # we expect op, start, stop
1107            self._nosplitCmds.append(cmd)
1108        elif _isLineCommand(cmd):
1109            # we expect op, start, stop, weight, colour, cap, dashes, join
1110            cmd = list(cmd)
1111            if len(cmd)<5: raise ValueError('bad line command '+ascii(cmd))
1112
1113            #determine line cap value at position 5. This can be str or numeric.
1114            if len(cmd)<6:
1115                cmd.append(1)
1116            else:
1117                cap = _convert2int(cmd[5], LINECAPS, 0, 2, 'cap', cmd)
1118                cmd[5] = cap
1119
1120            #dashes at index 6 - this is a dash array:
1121            if len(cmd)<7: cmd.append(None)
1122
1123            #join mode at index 7 - can be str or numeric, look up as for caps
1124            if len(cmd)<8: cmd.append(1)
1125            else:
1126                join = _convert2int(cmd[7], LINEJOINS, 0, 2, 'join', cmd)
1127                cmd[7] = join
1128
1129            #linecount at index 8.  Default is 1, set to 2 for double line.
1130            if len(cmd)<9: cmd.append(1)
1131            else:
1132                lineCount = cmd[8]
1133                if lineCount is None:
1134                    lineCount = 1
1135                    cmd[8] = lineCount
1136                assert lineCount >= 1
1137            #linespacing at index 9. Not applicable unless 2+ lines, defaults to line
1138            #width so you get a visible gap between centres
1139            if len(cmd)<10: cmd.append(cmd[3])
1140            else:
1141                space = cmd[9]
1142                if space is None:
1143                    space = cmd[3]
1144                    cmd[9] = space
1145            assert len(cmd) == 10
1146
1147            self._linecmds.append(tuple(cmd))
1148        elif cmd[0]=="ROUNDEDCORNERS":
1149            self._setCornerRadii(cmd[1])
1150        else:
1151            (op, (sc, sr), (ec, er)), values = cmd[:3] , cmd[3:]
1152            if sr in ('splitfirst','splitlast'):
1153                self._srflcmds.append(cmd)
1154            else:
1155                sc, ec, sr, er = self.normCellRange(sc,ec,sr,er)
1156                ec += 1
1157                for i in xrange(sr, er+1):
1158                    for j in xrange(sc, ec):
1159                        _setCellStyle(self._cellStyles, i, j, op, values)
1160
1161    def _drawLines(self):
1162        ccap, cdash, cjoin = None, None, None
1163        canv = self.canv
1164        canv.saveState()
1165
1166        rrd = self._roundingRectDef
1167        if rrd: #we are collection some lines
1168            SL = rrd.SL
1169            SL[:] = []  #empty saved lines list
1170            ocanvline = canv.line
1171            aSL = SL.append
1172            def rcCanvLine(xs, ys, xe, ye):
1173                if  (
1174                    (xs==xe and (xs>=rrd.x1 or xs<=rrd.x0)) #vertical line that needs to be saved
1175                    or
1176                    (ys==ye and (ys>=rrd.y1 or ys<=rrd.y0)) #horizontal line that needs to be saved
1177                    ):
1178                    aSL(RoundingRectLine(xs,ys,xe,ye,weight,color,cap,dash,join))
1179                else:
1180                    ocanvline(xs,ys,xe,ye)
1181            canv.line = rcCanvLine
1182
1183        try:
1184            for op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space in self._linecmds:
1185                if isinstance(sr,strTypes) and sr.startswith('split'): continue
1186                if cap!=None and ccap!=cap:
1187                    canv.setLineCap(cap)
1188                    ccap = cap
1189                if dash is None or dash == []:
1190                    if cdash is not None:
1191                        canv.setDash()
1192                        cdash = None
1193                elif dash != cdash:
1194                    canv.setDash(dash)
1195                    cdash = dash
1196                if join is not None and cjoin!=join:
1197                    canv.setLineJoin(join)
1198                    cjoin = join
1199                sc, ec, sr, er = self.normCellRange(sc,ec,sr,er)
1200                getattr(self,_LineOpMap.get(op, '_drawUnknown' ))( (sc, sr), (ec, er), weight, color, count, space)
1201        finally:
1202            if rrd:
1203                canv.line = ocanvline
1204        canv.restoreState()
1205        self._curcolor = None
1206
1207    def _drawUnknown(self,  start, end, weight, color, count, space):
1208        #we are only called from _drawLines which is one level up
1209        import sys
1210        op = sys._getframe(1).f_locals['op']
1211        raise ValueError("Unknown line command '%s'" % op)
1212
1213    def _drawGrid(self, start, end, weight, color, count, space):
1214        self._drawBox( start, end, weight, color, count, space)
1215        self._drawInnerGrid( start, end, weight, color, count, space)
1216
1217    def _drawBox(self,  start, end, weight, color, count, space):
1218        sc,sr = start
1219        ec,er = end
1220        self._drawHLines((sc, sr), (ec, sr), weight, color, count, space)
1221        self._drawHLines((sc, er+1), (ec, er+1), weight, color, count, space)
1222        self._drawVLines((sc, sr), (sc, er), weight, color, count, space)
1223        self._drawVLines((ec+1, sr), (ec+1, er), weight, color, count, space)
1224
1225    def _drawInnerGrid(self, start, end, weight, color, count, space):
1226        sc,sr = start
1227        ec,er = end
1228        self._drawHLines((sc, sr+1), (ec, er), weight, color, count, space)
1229        self._drawVLines((sc+1, sr), (ec, er), weight, color, count, space)
1230
1231    def _prepLine(self, weight, color):
1232        if color and color!=self._curcolor:
1233            self.canv.setStrokeColor(color)
1234            self._curcolor = color
1235        if weight and weight!=self._curweight:
1236            self.canv.setLineWidth(weight)
1237            self._curweight = weight
1238
1239    def _drawHLines(self, start, end, weight, color, count, space):
1240        sc,sr = start
1241        ec,er = end
1242        ecp = self._colpositions[sc:ec+2]
1243        rp = self._rowpositions[sr:er+1]
1244        if len(ecp)<=1 or len(rp)<1: return
1245        self._prepLine(weight, color)
1246        scp = ecp[0]
1247        ecp = ecp[-1]
1248        hBlocks = getattr(self,'_hBlocks',{})
1249        canvLine = self.canv.line
1250        if count == 1:
1251            for y in rp:
1252                _hLine(canvLine, scp, ecp, y, hBlocks)
1253        else:
1254            lf = lambda x0,y0,x1,y1,canvLine=canvLine, ws=weight+space, count=count: _multiLine(x0,x1,y0,canvLine,ws,count)
1255            for y in rp:
1256                _hLine(lf, scp, ecp, y, hBlocks)
1257
1258    def _drawHLinesB(self, start, end, weight, color, count, space):
1259        sc,sr = start
1260        ec,er = end
1261        self._drawHLines((sc, sr+1), (ec, er+1), weight, color, count, space)
1262
1263    def _drawVLines(self, start, end, weight, color, count, space):
1264        sc,sr = start
1265        ec,er = end
1266        erp = self._rowpositions[sr:er+2]
1267        cp  = self._colpositions[sc:ec+1]
1268        if len(erp)<=1 or len(cp)<1: return
1269        self._prepLine(weight, color)
1270        srp = erp[0]
1271        erp = erp[-1]
1272        vBlocks = getattr(self,'_vBlocks',{})
1273        canvLine = lambda y0, x0, y1, x1, _line=self.canv.line: _line(x0,y0,x1,y1)
1274        if count == 1:
1275            for x in cp:
1276                _hLine(canvLine, erp, srp, x, vBlocks)
1277        else:
1278            lf = lambda x0,y0,x1,y1,canvLine=canvLine, ws=weight+space, count=count: _multiLine(x0,x1,y0,canvLine,ws,count)
1279            for x in cp:
1280                _hLine(lf, erp, srp, x, vBlocks)
1281
1282    def _drawVLinesA(self, start, end, weight, color, count, space):
1283        sc,sr = start
1284        ec,er = end
1285        self._drawVLines((sc+1, sr), (ec+1, er), weight, color, count, space)
1286
1287    def wrap(self, availWidth, availHeight):
1288        self._calc(availWidth, availHeight)
1289        self.availWidth = availWidth
1290        return (self._width, self._height)
1291
1292    def onSplit(self,T,byRow=1):
1293        '''
1294        This method will be called when the Table is split.
1295        Special purpose tables can override to do special stuff.
1296        '''
1297        pass
1298
1299    def _cr_0(self,n,cmds,nr0,_srflMode=False):
1300        for c in cmds:
1301            (sc,sr), (ec,er) = c[1:3]
1302            if sr in ('splitfirst','splitlast'):
1303                if not _srflMode: continue
1304                self._addCommand(c)             #re-append the command
1305                if sr=='splitfirst': continue
1306                sr = er = n-1
1307            if sr<0: sr += nr0
1308            if sr>=n: continue
1309            if er>=n: er = n-1
1310            self._addCommand((c[0],)+((sc, sr), (ec, er))+tuple(c[3:]))
1311
1312    def _cr_1_1(self, n, nRows, repeatRows, cmds, _srflMode=False):
1313        nrr = len(repeatRows)
1314        rrS = set(repeatRows)
1315        for c in cmds:
1316            (sc,sr), (ec,er) = c[1:3]
1317            if sr in ('splitfirst','splitlast'):
1318                if not _srflMode: continue
1319                self._addCommand(c)
1320                if sr=='splitlast': continue
1321                sr = er = n
1322            if sr<0: sr += nRows
1323            if er<0: er += nRows
1324            cS = set(xrange(sr,er+1)) & rrS
1325            if cS:
1326                #it's a repeat row
1327                cS = list(cS)
1328                self._addCommand((c[0],)+((sc, repeatRows.index(min(cS))), (ec, repeatRows.index(max(cS))))+tuple(c[3:]))
1329            if er<n: continue
1330            sr = max(sr-n,0)+nrr
1331            er = max(er-n,0)+nrr
1332            self._addCommand((c[0],)+((sc, sr), (ec, er))+tuple(c[3:]))
1333        sr = self._rowSplitRange
1334        if sr:
1335            sr, er = sr
1336            if sr<0: sr += nRows
1337            if er<0: er += nRows
1338            if er<n:
1339                self._rowSplitRange = None
1340            else:
1341                sr = max(sr-n,0)+nrr
1342                er = max(er-n,0)+nrr
1343                self._rowSplitRange = sr,er
1344
1345    def _cr_1_0(self,n,cmds,_srflMode=False):
1346        for c in cmds:
1347            (sc,sr), (ec,er) = c[1:3]
1348            if sr in ('splitfirst','splitlast'):
1349                if not _srflMode: continue
1350                self._addCommand(c)
1351                if sr=='splitlast': continue
1352                sr = er = n
1353            if er>=0 and er<n: continue
1354            if sr>=0 and sr<n: sr=0
1355            if sr>=n: sr -= n
1356            if er>=n: er -= n
1357            self._addCommand((c[0],)+((sc, sr), (ec, er))+tuple(c[3:]))
1358
1359    def _splitRows(self,availHeight):
1360        n=self._getFirstPossibleSplitRowPosition(availHeight)
1361        repeatRows = self.repeatRows
1362        if n<= (repeatRows if isinstance(repeatRows,int) else (max(repeatRows)+1)): return []
1363        lim = len(self._rowHeights)
1364        if n==lim: return [self]
1365
1366        lo = self._rowSplitRange
1367        if lo:
1368            lo, hi = lo
1369            if lo<0: lo += lim
1370            if hi<0: hi += lim
1371            if n>hi:
1372                return self._splitRows(availHeight - sum(self._rowHeights[hi:n]))
1373            elif n<lo:
1374                return []
1375
1376        repeatCols = self.repeatCols
1377        splitByRow = self.splitByRow
1378        data = self._cellvalues
1379
1380        #we're going to split into two superRows
1381        ident = self.ident
1382        if ident: ident = IdentStr(ident)
1383        lto = self._longTableOptimize
1384        if lto:
1385            splitH = self._rowHeights
1386        else:
1387            splitH = self._argH
1388        cornerRadii = getattr(self,'_cornerRadii',None)
1389        R0 = self.__class__( data[:n], colWidths=self._colWidths, rowHeights=splitH[:n],
1390                repeatRows=repeatRows, repeatCols=repeatCols,
1391                splitByRow=splitByRow, normalizedData=1, cellStyles=self._cellStyles[:n],
1392                ident=ident,
1393                spaceBefore=getattr(self,'spaceBefore',None),
1394                longTableOptimize=lto,
1395                cornerRadii=cornerRadii[:2] if cornerRadii else None)
1396
1397        nrows = self._nrows
1398        ncols = self._ncols
1399        #copy the commands
1400        A = []
1401        # hack up the line commands
1402        for op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space in self._linecmds:
1403            if isinstance(sr,strTypes) and sr.startswith('split'):
1404                A.append((op,(sc,sr), (ec,sr), weight, color, cap, dash, join, count, space))
1405                if sr=='splitlast':
1406                    sr = er = n-1
1407                elif sr=='splitfirst':
1408                    sr = n
1409                    er = n
1410
1411            if sc < 0: sc += ncols
1412            if ec < 0: ec += ncols
1413            if sr < 0: sr += nrows
1414            if er < 0: er += nrows
1415
1416            if op in ('BOX','OUTLINE','GRID'):
1417                if sr<n and er>=n:
1418                    # we have to split the BOX
1419                    A.append(('LINEABOVE',(sc,sr), (ec,sr), weight, color, cap, dash, join, count, space))
1420                    A.append(('LINEBEFORE',(sc,sr), (sc,er), weight, color, cap, dash, join, count, space))
1421                    A.append(('LINEAFTER',(ec,sr), (ec,er), weight, color, cap, dash, join, count, space))
1422                    A.append(('LINEBELOW',(sc,er), (ec,er), weight, color, cap, dash, join, count, space))
1423                    if op=='GRID':
1424                        A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space))
1425                        A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space))
1426                        A.append(('INNERGRID',(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
1427                else:
1428                    A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
1429            elif op == 'INNERGRID':
1430                if sr<n and er>=n:
1431                    A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space))
1432                    A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space))
1433                A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
1434            elif op == 'LINEBELOW':
1435                if sr<n and er>=(n-1):
1436                    A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space))
1437                A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
1438            elif op == 'LINEABOVE':
1439                if sr<=n and er>=n:
1440                    A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space))
1441                A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
1442            else:
1443                A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
1444
1445        R0._cr_0(n,A,nrows)
1446        R0._cr_0(n,self._bkgrndcmds,nrows,_srflMode=True)
1447        R0._cr_0(n,self._spanCmds,nrows)
1448        R0._cr_0(n,self._nosplitCmds,nrows)
1449        for c in self._srflcmds:
1450            R0._addCommand(c)
1451            if c[1][1]!='splitlast': continue
1452            (sc,sr), (ec,er) = c[1:3]
1453            R0._addCommand((c[0],)+((sc, n-1), (ec, n-1))+tuple(c[3:]))
1454
1455        if ident: ident = IdentStr(ident)
1456        if repeatRows:
1457            if isinstance(repeatRows,int):
1458                iRows = data[:repeatRows]
1459                iRowH = splitH[:repeatRows]
1460                iCS = self._cellStyles[:repeatRows]
1461                repeatRows = list(xrange(repeatRows))
1462            else:
1463                #we have a list of repeated rows eg (1,3)
1464                repeatRows = list(sorted(repeatRows))
1465                iRows = [data[i] for i in repeatRows]
1466                iRowH = [splitH[i] for i in repeatRows]
1467                iCS = [self._cellStyles[i] for i in repeatRows]
1468            R1 = self.__class__(iRows+data[n:],colWidths=self._colWidths,
1469                    rowHeights=iRowH+splitH[n:],
1470                    repeatRows=len(repeatRows), repeatCols=repeatCols,
1471                    splitByRow=splitByRow, normalizedData=1,
1472                    cellStyles=iCS+self._cellStyles[n:],
1473                    ident=ident,
1474                    spaceAfter=getattr(self,'spaceAfter',None),
1475                    longTableOptimize=lto,
1476                    cornerRadii = cornerRadii,
1477                    )
1478            R1._cr_1_1(n,nrows,repeatRows,A) #linecommands
1479            R1._cr_1_1(n,nrows,repeatRows,self._bkgrndcmds,_srflMode=True)
1480            R1._cr_1_1(n,nrows,repeatRows,self._spanCmds)
1481            R1._cr_1_1(n,nrows,repeatRows,self._nosplitCmds)
1482        else:
1483            #R1 = slelf.__class__(data[n:], self._argW, self._argH[n:],
1484            R1 = self.__class__(data[n:], colWidths=self._colWidths, rowHeights=splitH[n:],
1485                    repeatRows=repeatRows, repeatCols=repeatCols,
1486                    splitByRow=splitByRow, normalizedData=1, cellStyles=self._cellStyles[n:],
1487                    ident=ident,
1488                    spaceAfter=getattr(self,'spaceAfter',None),
1489                    longTableOptimize=lto,
1490                    cornerRadii = ([0,0] + cornerRadii[2:]) if cornerRadii else None,
1491                    )
1492            R1._cr_1_0(n,A)
1493            R1._cr_1_0(n,self._bkgrndcmds,_srflMode=True)
1494            R1._cr_1_0(n,self._spanCmds)
1495            R1._cr_1_0(n,self._nosplitCmds)
1496        for c in self._srflcmds:
1497            R1._addCommand(c)
1498            if c[1][1]!='splitfirst': continue
1499            (sc,sr), (ec,er) = c[1:3]
1500            R1._addCommand((c[0],)+((sc, 0), (ec, 0))+tuple(c[3:]))
1501
1502        R0.hAlign = R1.hAlign = self.hAlign
1503        R0.vAlign = R1.vAlign = self.vAlign
1504        self.onSplit(R0)
1505        self.onSplit(R1)
1506        return [R0,R1]
1507
1508    def _getRowImpossible(impossible,cells,ranges):
1509        for xy in cells:
1510            r=ranges[xy]
1511            if r!=None:
1512                y1,y2=r[1],r[3]
1513                if y1!=y2:
1514                    ymin=min(y1,y2) #normalize
1515                    ymax=max(y1,y2) #normalize
1516                    y=ymin+1
1517                    while 1:
1518                        if y>ymax: break
1519                        impossible[y]=None #split at position y is impossible because of overlapping rowspan
1520                        y+=1
1521    _getRowImpossible=staticmethod(_getRowImpossible)
1522
1523    def _getFirstPossibleSplitRowPosition(self,availHeight):
1524        impossible={}
1525        if self._spanCmds:
1526            self._getRowImpossible(impossible,self._rowSpanCells,self._spanRanges)
1527        if self._nosplitCmds:
1528            self._getRowImpossible(impossible,self._rowNoSplitCells,self._nosplitRanges)
1529        h = 0
1530        n = 1
1531        split_at = 0 # from this point of view 0 is the first position where the table may *always* be splitted
1532        for rh in self._rowHeights:
1533            if h+rh>availHeight:
1534                break
1535            if n not in impossible:
1536                split_at=n
1537            h=h+rh
1538            n=n+1
1539        return split_at
1540
1541    def split(self, availWidth, availHeight):
1542        self._calc(availWidth, availHeight)
1543        if self.splitByRow:
1544            if not rl_config.allowTableBoundsErrors and self._width>availWidth: return []
1545            return self._splitRows(availHeight)
1546        else:
1547            raise NotImplementedError
1548
1549    def _makeRoundedCornersClip(self, FUZZ=rl_config._FUZZ):
1550        self._roundingRectDef = None
1551        cornerRadii = getattr(self,'_cornerRadii',None)
1552        if not cornerRadii or max(cornerRadii)<=FUZZ: return
1553        nrows = self._nrows
1554        ncols = self._ncols
1555        ar = [min(self._rowHeights[i],self._colWidths[j],cornerRadii[k]) for
1556                k,(i,j) in enumerate((
1557                    (0,0),
1558                    (0,ncols-1),
1559                    (nrows-1,0),
1560                    (nrows-1, ncols-1),
1561                    ))]
1562        rp = self._rowpositions
1563        cp = self._colpositions
1564
1565        x0 = cp[0]
1566        y0 = rp[nrows]
1567        x1 = cp[ncols]
1568        y1 = rp[0]
1569        w = x1 - x0
1570        h = y1 - y0
1571        self._roundingRectDef = RoundingRectDef(x0, y0, w, h, x1, y1, ar, [])
1572        P = self.canv.beginPath()
1573        P.roundRect(x0, y0, w, h, ar)
1574        c = self.canv
1575        c.addLiteral('%begin table rect clip')
1576        c.clipPath(P,stroke=0)
1577        c.addLiteral('%end table rect clip')
1578
1579    def _restoreRoundingObscuredLines(self):
1580        x0, y0, w, h, x1, y1, ar, SL = self._roundingRectDef
1581        if not SL: return
1582        canv = self.canv
1583        canv.saveState()
1584        ccap = cdash = cjoin = self._curweight = self._curcolor = None
1585        line = canv.line
1586        cornerRadii = self._cornerRadii
1587        for (xs,ys,xe,ye,weight,color,cap,dash,join) in SL:
1588            if cap!=None and ccap!=cap:
1589                canv.setLineCap(cap)
1590                ccap = cap
1591            if dash is None or dash == []:
1592                if cdash is not None:
1593                    canv.setDash()
1594                    cdash = None
1595            elif dash != cdash:
1596                canv.setDash(dash)
1597                cdash = dash
1598            if join is not None and cjoin!=join:
1599                canv.setLineJoin(join)
1600                cjoin = join
1601            self._prepLine(weight, color)
1602            if ys==ye:
1603                #horizontal line
1604                if ys>y1 or ys<y0:
1605                    line(xs,ys,xe,ye)   #simple line that's outside the clip
1606                    continue
1607                #which corners are involved
1608                if ys==y0:
1609                    ypos = 'bottom'
1610                    r0 = ar[2]
1611                    r1 = ar[3]
1612                else: #ys==y1
1613                    ypos = 'top'
1614                    r0 = ar[0]
1615                    r1 = ar[1]
1616                if xs>=x0+r0 and xe<=x1-r1:
1617                    line(xs,ys,xe,ye)   #simple line with no rounding
1618                    continue
1619                #we have some rounding so we must use a path
1620                c0 = _quadrantDef('left',ypos,(xs,ys), r0, kind=2, direction='left-right') if xs<x0+r0 else None
1621                c1 = _quadrantDef('right',ypos,(xe,ye), r1, kind=1, direction='left-right') if xe>x1-r1 else None
1622            else:
1623                #vertical line
1624                if xs>x1 or xs<x0:
1625                    line(xs,ys,xe,ye)   #simple line that's outside the clip
1626                    continue
1627                #which corners are involved
1628                if xs==x0:
1629                    xpos = 'left'
1630                    r0 = ar[2]
1631                    r1 = ar[0]
1632                else: #xs==x1
1633                    xpos = 'right'
1634                    r0 = ar[3]
1635                    r1 = ar[1]
1636                if ys>=y0+r0 and ye<=y1-r1:
1637                    line(xs,ys,xe,ye)   #simple line with no rounding
1638                    continue
1639                #we have some rounding so we must use a path
1640                c0 = _quadrantDef(xpos,'bottom',(xs,ys), r0, kind=2, direction='bottom-top') if ys<y0+r0 else None
1641                c1 = _quadrantDef(xpos,'top',(xe,ye), r1, kind=1, direction='bottom-top') if ye>y1-r1 else None
1642            P = canv.beginPath()
1643            if c0:
1644                P.moveTo(*c0[0])
1645                P.curveTo(c0[1][0],c0[1][1],c0[2][0],c0[2][1], c0[3][0],c0[3][1])
1646            else:
1647                P.moveTo(xs,ys)
1648            if not c1:
1649                P.lineTo(xe,ye)
1650            else:
1651                P.lineTo(*c1[0])
1652                P.curveTo(c1[1][0],c1[1][1],c1[2][0],c1[2][1], c1[3][0],c1[3][1])
1653            canv.drawPath(P, stroke=1, fill=0)
1654        canv.restoreState()
1655
1656    def draw(self):
1657        c = self.canv
1658        c.saveState()
1659        self._makeRoundedCornersClip()
1660        self._curweight = self._curcolor = self._curcellstyle = None
1661        self._drawBkgrnd()
1662        if not self._spanCmds:
1663            # old fashioned case, no spanning, steam on and do each cell
1664            for row, rowstyle, rowpos, rowheight in zip(self._cellvalues, self._cellStyles, self._rowpositions[1:], self._rowHeights):
1665                for cellval, cellstyle, colpos, colwidth in zip(row, rowstyle, self._colpositions[:-1], self._colWidths):
1666                    self._drawCell(cellval, cellstyle, (colpos, rowpos), (colwidth, rowheight))
1667        else:
1668            # we have some row or col spans, need a more complex algorithm
1669            # to find the rect for each
1670            for rowNo in xrange(self._nrows):
1671                for colNo in xrange(self._ncols):
1672                    cellRect = self._spanRects[colNo, rowNo]
1673                    if cellRect is not None:
1674                        (x, y, width, height) = cellRect
1675                        cellval = self._cellvalues[rowNo][colNo]
1676                        cellstyle = self._cellStyles[rowNo][colNo]
1677                        self._drawCell(cellval, cellstyle, (x, y), (width, height))
1678        self._drawLines()
1679        c.restoreState()
1680        if self._roundingRectDef:
1681            self._restoreRoundingObscuredLines()
1682
1683    def _drawBkgrnd(self):
1684        nrows = self._nrows
1685        ncols = self._ncols
1686        canv = self.canv
1687        colpositions = self._colpositions
1688        rowpositions = self._rowpositions
1689        rowHeights = self._rowHeights
1690        colWidths = self._colWidths
1691        spanRects = getattr(self,'_spanRects',None)
1692        for cmd, (sc, sr), (ec, er), arg in self._bkgrndcmds:
1693            if sr in ('splitfirst','splitlast'): continue
1694            if sc < 0: sc = sc + ncols
1695            if ec < 0: ec = ec + ncols
1696            if sr < 0: sr = sr + nrows
1697            if er < 0: er = er + nrows
1698            x0 = colpositions[sc]
1699            y0 = rowpositions[sr]
1700            x1 = colpositions[min(ec+1,ncols)]
1701            y1 = rowpositions[min(er+1,nrows)]
1702            w, h = x1-x0, y1-y0
1703            if hasattr(arg,'__call__'):
1704                arg(self,canv, x0, y0, w, h)
1705            elif cmd == 'ROWBACKGROUNDS':
1706                #Need a list of colors to cycle through.  The arguments
1707                #might be already colours, or convertible to colors, or
1708                # None, or the str 'None'.
1709                #It's very common to alternate a pale shade with None.
1710                colorCycle = list(map(colors.toColorOrNone, arg))
1711                count = len(colorCycle)
1712                rowCount = er - sr + 1
1713                for i in xrange(rowCount):
1714                    color = colorCycle[i%count]
1715                    h = rowHeights[sr + i]
1716                    if color:
1717                        canv.setFillColor(color)
1718                        canv.rect(x0, y0, w, -h, stroke=0,fill=1)
1719                    y0 = y0 - h
1720            elif cmd == 'COLBACKGROUNDS':
1721                #cycle through colours columnwise
1722                colorCycle = list(map(colors.toColorOrNone, arg))
1723                count = len(colorCycle)
1724                colCount = ec - sc + 1
1725                for i in xrange(colCount):
1726                    color = colorCycle[i%count]
1727                    w = colWidths[sc + i]
1728                    if color:
1729                        canv.setFillColor(color)
1730                        canv.rect(x0, y0, w, h, stroke=0,fill=1)
1731                    x0 = x0 +w
1732            else:   #cmd=='BACKGROUND'
1733                if arg and isinstance(arg,(list,tuple)) and arg[0] in ('VERTICAL','HORIZONTAL'):
1734                    #
1735                    # Arg is a list, assume we are going for a gradient fill
1736                    # where we expect a containing a direction for the gradient
1737                    # and the starting an final gradient colors. For example:
1738                    # ['HORIZONTAL', colors.white, colors.grey]   or
1739                    # ['VERTICAL', colors.red, colors.blue]
1740                    #
1741                    canv.saveState()
1742
1743                    if ec==sc and er==sr and spanRects:
1744                        xywh = spanRects.get((sc,sr))
1745                        if xywh:
1746                            #it's a single cell
1747                            x0, y0, w, h = xywh
1748                    p = canv.beginPath()
1749                    p.rect(x0, y0, w, h)
1750                    canv.clipPath(p, stroke=0)
1751                    direction=arg.pop(0)
1752                    if direction=="HORIZONTAL":
1753                        canv.linearGradient(x0,y0,x0+w,y0,arg,extend=False)
1754                    else:   #VERTICAL
1755                        canv.linearGradient(x0,y0,x0,y0+h,arg,extend=False)
1756                    canv.restoreState()
1757                else:
1758                    color = colors.toColorOrNone(arg)
1759                    if color:
1760                        if ec==sc and er==sr and spanRects:
1761                            xywh = spanRects.get((sc,sr))
1762                            if xywh:
1763                                #it's a single cell
1764                                x0, y0, w, h = xywh
1765                        canv.setFillColor(color)
1766                        canv.rect(x0, y0, w, h, stroke=0,fill=1)
1767
1768    def _drawCell(self, cellval, cellstyle, pos, size):
1769        colpos, rowpos = pos
1770        colwidth, rowheight = size
1771        if self._curcellstyle is not cellstyle:
1772            cur = self._curcellstyle
1773            if cur is None or cellstyle.color != cur.color:
1774                self.canv.setFillColor(cellstyle.color)
1775            if cur is None or cellstyle.leading != cur.leading or cellstyle.fontname != cur.fontname or cellstyle.fontsize != cur.fontsize:
1776                self.canv.setFont(cellstyle.fontname, cellstyle.fontsize, cellstyle.leading)
1777            self._curcellstyle = cellstyle
1778
1779        just = cellstyle.alignment
1780        valign = cellstyle.valign
1781        if isinstance(cellval,(tuple,list,Flowable)):
1782            if not isinstance(cellval,(tuple,list)): cellval = (cellval,)
1783            # we assume it's a list of Flowables
1784            W = []
1785            H = []
1786            w, h = self._listCellGeom(cellval,colwidth,cellstyle,W=W, H=H,aH=rowheight)
1787            if valign=='TOP':
1788                y = rowpos + rowheight - cellstyle.topPadding
1789            elif valign=='BOTTOM':
1790                y = rowpos+cellstyle.bottomPadding + h
1791            else:
1792                y = rowpos+(rowheight+cellstyle.bottomPadding-cellstyle.topPadding+h)/2.0
1793            if cellval: y += cellval[0].getSpaceBefore()
1794            for v, w, h in zip(cellval,W,H):
1795                if just=='LEFT': x = colpos+cellstyle.leftPadding
1796                elif just=='RIGHT': x = colpos+colwidth-cellstyle.rightPadding - w
1797                elif just in ('CENTRE', 'CENTER'):
1798                    x = colpos+(colwidth+cellstyle.leftPadding-cellstyle.rightPadding-w)/2.0
1799                else:
1800                    raise ValueError('Invalid justification %s' % just)
1801                y -= v.getSpaceBefore()
1802                y -= h
1803                v.drawOn(self.canv,x,y)
1804                y -= v.getSpaceAfter()
1805        else:
1806            if just == 'LEFT':
1807                draw = self.canv.drawString
1808                x = colpos + cellstyle.leftPadding
1809            elif just in ('CENTRE', 'CENTER'):
1810                draw = self.canv.drawCentredString
1811                x = colpos+(colwidth+cellstyle.leftPadding-cellstyle.rightPadding)*0.5
1812            elif just == 'RIGHT':
1813                draw = self.canv.drawRightString
1814                x = colpos + colwidth - cellstyle.rightPadding
1815            elif just == 'DECIMAL':
1816                draw = self.canv.drawAlignedString
1817                x = colpos + colwidth - cellstyle.rightPadding
1818            else:
1819                raise ValueError('Invalid justification %s' % just)
1820            vals = str(cellval).split("\n")
1821            n = len(vals)
1822            leading = cellstyle.leading
1823            fontsize = cellstyle.fontsize
1824            if valign=='BOTTOM':
1825                y = rowpos + cellstyle.bottomPadding+n*leading-fontsize
1826            elif valign=='TOP':
1827                y = rowpos + rowheight - cellstyle.topPadding - fontsize
1828            elif valign=='MIDDLE':
1829                #tim roberts pointed out missing fontsize correction 2004-10-04
1830                y = rowpos + (cellstyle.bottomPadding + rowheight-cellstyle.topPadding+n*leading)/2.0 - fontsize
1831            else:
1832                raise ValueError("Bad valign: '%s'" % str(valign))
1833
1834            for v in vals:
1835                draw(x, y, v)
1836                y -= leading
1837            onDraw = getattr(cellval,'onDraw',None)
1838            if onDraw:
1839                onDraw(self.canv,cellval.kind,cellval.label)
1840
1841        if cellstyle.href:
1842            #external hyperlink
1843            self.canv.linkURL(cellstyle.href, (colpos, rowpos, colpos + colwidth, rowpos + rowheight), relative=1)
1844        if cellstyle.destination:
1845            #external hyperlink
1846            self.canv.linkRect("", cellstyle.destination, Rect=(colpos, rowpos, colpos + colwidth, rowpos + rowheight), relative=1)
1847
1848    def _setCornerRadii(self, cornerRadii):
1849        if isListOfNumbersOrNone(cornerRadii):
1850            self._cornerRadii = None if not cornerRadii else list(cornerRadii) + (max(4-len(cornerRadii),0)*[0])
1851        else:
1852            raise ValueError('cornerRadii should be None or a list/tuple of numeric radii')
1853
1854_LineOpMap = {  'GRID':'_drawGrid',
1855                'BOX':'_drawBox',
1856                'OUTLINE':'_drawBox',
1857                'INNERGRID':'_drawInnerGrid',
1858                'LINEBELOW':'_drawHLinesB',
1859                'LINEABOVE':'_drawHLines',
1860                'LINEBEFORE':'_drawVLines',
1861                'LINEAFTER':'_drawVLinesA', }
1862
1863class LongTable(Table):
1864    '''Henning von Bargen's changes will be active'''
1865    _longTableOptimize = 1
1866
1867LINECOMMANDS = list(_LineOpMap.keys())
1868
1869def _isLineCommand(cmd):
1870    return cmd[0] in LINECOMMANDS
1871
1872def _setCellStyle(cellStyles, i, j, op, values):
1873    #new = CellStyle('<%d, %d>' % (i,j), cellStyles[i][j])
1874    #cellStyles[i][j] = new
1875    ## modify in place!!!
1876    new = cellStyles[i][j]
1877    if op == 'FONT':
1878        n = len(values)
1879        new.fontname = values[0]
1880        if n>1:
1881            new.fontsize = values[1]
1882            if n>2:
1883                new.leading = values[2]
1884            else:
1885                new.leading = new.fontsize*1.2
1886    elif op in ('FONTNAME', 'FACE'):
1887        new.fontname = values[0]
1888    elif op in ('SIZE', 'FONTSIZE'):
1889        new.fontsize = values[0]
1890    elif op == 'LEADING':
1891        new.leading = values[0]
1892    elif op == 'TEXTCOLOR':
1893        new.color = colors.toColor(values[0], colors.Color(0,0,0))
1894    elif op in ('ALIGN', 'ALIGNMENT'):
1895        new.alignment = values[0]
1896    elif op == 'VALIGN':
1897        new.valign = values[0]
1898    elif op == 'LEFTPADDING':
1899        new.leftPadding = values[0]
1900    elif op == 'RIGHTPADDING':
1901        new.rightPadding = values[0]
1902    elif op == 'TOPPADDING':
1903        new.topPadding = values[0]
1904    elif op == 'BOTTOMPADDING':
1905        new.bottomPadding = values[0]
1906    elif op == 'HREF':
1907        new.href = values[0]
1908    elif op == 'DESTINATION':
1909        new.destination = values[0]
1910
1911GRID_STYLE = TableStyle(
1912    [('GRID', (0,0), (-1,-1), 0.25, colors.black),
1913     ('ALIGN', (1,1), (-1,-1), 'RIGHT')]
1914    )
1915BOX_STYLE = TableStyle(
1916    [('BOX', (0,0), (-1,-1), 0.50, colors.black),
1917     ('ALIGN', (1,1), (-1,-1), 'RIGHT')]
1918    )
1919LABELED_GRID_STYLE = TableStyle(
1920    [('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
1921     ('BOX', (0,0), (-1,-1), 2, colors.black),
1922     ('LINEBELOW', (0,0), (-1,0), 2, colors.black),
1923     ('LINEAFTER', (0,0), (0,-1), 2, colors.black),
1924     ('ALIGN', (1,1), (-1,-1), 'RIGHT')]
1925    )
1926COLORED_GRID_STYLE = TableStyle(
1927    [('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
1928     ('BOX', (0,0), (-1,-1), 2, colors.red),
1929     ('LINEBELOW', (0,0), (-1,0), 2, colors.black),
1930     ('LINEAFTER', (0,0), (0,-1), 2, colors.black),
1931     ('ALIGN', (1,1), (-1,-1), 'RIGHT')]
1932    )
1933LIST_STYLE = TableStyle(
1934    [('LINEABOVE', (0,0), (-1,0), 2, colors.green),
1935     ('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black),
1936     ('LINEBELOW', (0,-1), (-1,-1), 2, colors.green),
1937     ('ALIGN', (1,1), (-1,-1), 'RIGHT')]
1938    )
1939
1940# experimental iterator which can apply a sequence
1941# of colors e.g. Blue, None, Blue, None as you move
1942# down.
1943if __name__ == '__main__':
1944    from tests.test_platypus_tables import old_tables_test
1945    old_tables_test()
1946