Canvas.roundedrect allows variable radii; tables can have rounded corners; version --> 3.5.58
authorrobin
Fri, 01 Jan 2021 17:42:23 +0000
changeset 4632 0a4d240bd537
parent 4631 d3da33055d77
child 4633 895acd8868ff
Canvas.roundedrect allows variable radii; tables can have rounded corners; version --> 3.5.58
CHANGES.md
src/reportlab/__init__.py
src/reportlab/pdfgen/pathobject.py
src/reportlab/platypus/tables.py
tests/test_pdfgen_general.py
tests/test_platypus_tables.py
--- a/CHANGES.md	Sun Dec 27 11:36:08 2020 +0000
+++ b/CHANGES.md	Fri Jan 01 17:42:23 2021 +0000
@@ -11,6 +11,11 @@
 The contributors lists are in no order and apologies to those accidentally not
 mentioned. If we missed you, please let us know!
 
+CHANGES  3.5.58	 01/01/2021
+---------------------------
+	* Allow variant corners in Canvas.roundRect
+	* Allow tables to have rounded corners
+
 CHANGES  3.5.57	 27/12/2020
 ---------------------------
 	* added ddfStyle to Label
--- a/src/reportlab/__init__.py	Sun Dec 27 11:36:08 2020 +0000
+++ b/src/reportlab/__init__.py	Fri Jan 01 17:42:23 2021 +0000
@@ -1,9 +1,9 @@
 #Copyright ReportLab Europe Ltd. 2000-2018
 #see license.txt for license details
 __doc__="""The Reportlab PDF generation library."""
-Version = "3.5.57"
+Version = "3.5.58"
 __version__=Version
-__date__='20201227'
+__date__='20210101'
 
 import sys, os
 
--- a/src/reportlab/pdfgen/pathobject.py	Sun Dec 27 11:36:08 2020 +0000
+++ b/src/reportlab/pdfgen/pathobject.py	Fri Jan 01 17:42:23 2021 +0000
@@ -25,7 +25,7 @@
 
     Path objects are probably not long, so we pack onto one line
 
-    the code argument allows a canvas to get the operatiosn appended directly so
+    the code argument allows a canvas to get the operations appended directly so
     avoiding the final getCode
     """
     def __init__(self,code=None):
@@ -95,31 +95,43 @@
         #use a precomputed set of factors for the bezier approximation
         #to a circle. There are six relevant points on the x axis and y axis.
         #sketch them and it should all make sense!
-        t = 0.4472 * radius
-
-        x0 = x
-        x1 = x0 + t
-        x2 = x0 + radius
-        x3 = x0 + width - radius
-        x4 = x0 + width - t
-        x5 = x0 + width
-
-        y0 = y
-        y1 = y0 + t
-        y2 = y0 + radius
-        y3 = y0 + height - radius
-        y4 = y0 + height - t
-        y5 = y0 + height
-
-        self.moveTo(x2, y0)
-        self.lineTo(x3, y0) #bottom row
-        self.curveTo(x4, y0, x5, y1, x5, y2) #bottom right
-        self.lineTo(x5, y3) #right edge
-        self.curveTo(x5, y4, x4, y5, x3, y5) #top right
-        self.lineTo(x2, y5) #top row
-        self.curveTo(x1, y5, x0, y4, x0, y3) #top left
-        self.lineTo(x0, y2) #left edge
-        self.curveTo(x0, y1, x1, y0, x2, y0) #bottom left
+        m = 0.4472  #radius multiplier
+        xhi = x,x+width
+        xlo, xhi = min(xhi), max(xhi)
+        yhi = y,y+height
+        ylo, yhi = min(yhi), max(yhi)
+        if isinstance(radius,(list,tuple)):
+            r = [max(0,r) for r in radius]
+            if len(r)<4: r += (4-len(r))*[0]
+            self.moveTo(xlo + r[2], ylo)    #start at bottom left
+            self.lineTo(xhi - r[3], ylo)    #bottom row
+            if r[3]>0:
+                t = m*r[3]
+                self.curveTo(xhi - t, ylo, xhi, ylo + t, xhi, ylo + r[3]) #bottom right
+            self.lineTo(xhi, yhi - r[1]) #right edge
+            if r[1]>0:
+                t = m*r[1]
+                self.curveTo(xhi, yhi - t, xhi - t, yhi, xhi - r[1], yhi) #top right
+            self.lineTo(xlo + r[0], yhi) #top row
+            if r[0]>0:
+                t = m*r[0]
+                self.curveTo(xlo + t, yhi, xlo, yhi - t, xlo, yhi - r[0]) #top left
+            self.lineTo(xlo, ylo + r[2]) #left edge
+            if r[2]>0:
+                t = m*r[2]
+                self.curveTo(xlo, ylo + t, xlo + t, ylo, xlo + r[2], ylo) #bottom left
+            # 4 radii top left top right bittom left bottom right
+        else:
+            t = m * radius
+            self.moveTo(xlo + radius, ylo)
+            self.lineTo(xhi - radius, ylo) #bottom row
+            self.curveTo(xhi - t, ylo, xhi, ylo + t, xhi, ylo + radius) #bottom right
+            self.lineTo(xhi, yhi - radius) #right edge
+            self.curveTo(xhi, yhi - t, xhi - t, yhi, xhi - radius, yhi) #top right
+            self.lineTo(xlo + radius, yhi) #top row
+            self.curveTo(xlo + t, yhi, xlo, yhi - t, xlo, yhi - radius) #top left
+            self.lineTo(xlo, ylo + radius) #left edge
+            self.curveTo(xlo, ylo + t, xlo + t, ylo, xlo + radius, ylo) #bottom left
         self.close()
 
     def close(self):
--- a/src/reportlab/platypus/tables.py	Sun Dec 27 11:36:08 2020 +0000
+++ b/src/reportlab/platypus/tables.py	Fri Jan 01 17:42:23 2021 +0000
@@ -28,11 +28,13 @@
 from reportlab.lib.styles import PropertySet, ParagraphStyle, _baseFontName
 from reportlab.lib import colors
 from reportlab.lib.utils import annotateException, IdentStr, flatten, isStr, asNative, strTypes
+from reportlab.lib.validators import isListOfNumbersOrNone
 from reportlab.lib.rl_accel import fp_str
 from reportlab.lib.abag import ABag as CellFrame
 from reportlab.pdfbase.pdfmetrics import stringWidth
 from reportlab.platypus.doctemplate import Indenter, NullActionFlowable
 from reportlab.platypus.flowables import LIIndenter
+from collections import namedtuple
 
 LINECAPS={None: None, 'butt':0,'round':1,'projecting':2,'squared':2}
 LINEJOINS={None: None, 'miter':0, 'mitre':0, 'round':1,'bevel':2}
@@ -138,6 +140,41 @@
             R[j] = v
     return R
 
+def _calcBezierPoints(P, kind):
+    '''calculate all or half of a bezier curve
+    kind==0 all, 1=first half else second half''' 
+    if kind==0:
+        return P
+    else:
+        Q0 = (0.5*(P[0][0]+P[1][0]),0.5*(P[0][1]+P[1][1]))
+        Q1 = (0.5*(P[1][0]+P[2][0]),0.5*(P[1][1]+P[2][1]))
+        Q2 = (0.5*(P[2][0]+P[3][0]),0.5*(P[2][1]+P[3][1]))
+        R0 = (0.5*(Q0[0]+Q1[0]),0.5*(Q0[1]+Q1[1]))
+        R1 = (0.5*(Q1[0]+Q2[0]),0.5*(Q1[1]+Q2[1]))
+        S0 = (0.5*(R0[0]+R1[0]),0.5*(R0[1]+R1[1]))
+        return [P[0],Q0,R0,S0] if kind==1 else [S0,R1,Q2,P[3]]
+
+def _quadrantDef(xpos, ypos, corner, r, kind=0, direction='left-right', m=0.4472):
+    t = m*r
+    if xpos=='right' and ypos=='bottom': #bottom right
+        xhi,ylo = corner
+        P = [(xhi - r, ylo),(xhi-t, ylo), (xhi, ylo + t), (xhi, ylo + r)]
+    elif xpos=='right' and ypos=='top': #top right
+        xhi,yhi = corner
+        P = [(xhi, yhi - r),(xhi, yhi - t), (xhi - t, yhi), (xhi - r, yhi)]
+    elif xpos=='left' and ypos=='top': #top left
+        xlo,yhi = corner
+        P = [(xlo + r, yhi),(xlo + t, yhi), (xlo, yhi - t), (xlo, yhi - r)]
+    elif xpos=='left' and ypos=='bottom': #bottom left
+        xlo,ylo = corner
+        P = [(xlo, ylo + r),(xlo, ylo + t), (xlo + t, ylo), (xlo + r, ylo)]
+    else:
+        raise ValueError('Unknown quadrant position %s' % repr((xpos,ypos)))
+    if direction=='left-right' and P[0][0]>P[-1][0] or direction=='bottom-top' and P[0][1]>P[-1][1]:
+        P.reverse()
+    P = _calcBezierPoints(P, kind)
+    return P
+
 def _hLine(canvLine, scp, ecp, y, hBlocks, FUZZ=rl_config._FUZZ):
     '''
     Draw horizontal lines; do not draw through regions specified in hBlocks
@@ -208,11 +245,17 @@
 class _ExpandedCellTuple(tuple):
     pass
 
+
+RoundingRectDef = namedtuple('RoundingRectDefs','x0 y0 w h x1 y1 ar SL')
+RoundingRectLine = namedtuple('RoundingRectLine','xs ys xe ye weight color cap dash join')
+
 class Table(Flowable):
     def __init__(self, data, colWidths=None, rowHeights=None, style=None,
                 repeatRows=0, repeatCols=0, splitByRow=1, emptyTableAction=None, ident=None,
                 hAlign=None,vAlign=None, normalizedData=0, cellStyles=None, rowSplitRange=None,
-                spaceBefore=None,spaceAfter=None, longTableOptimize=None, minRowHeights=None):
+                spaceBefore=None,spaceAfter=None, longTableOptimize=None, minRowHeights=None,
+                cornerRadii=None, #or [topLeft, topRight, bottomLeft bottomRight]
+                ):
         self.ident = ident
         self.hAlign = hAlign or 'CENTER'
         self.vAlign = vAlign or 'MIDDLE'
@@ -294,6 +337,9 @@
         self.repeatCols = repeatCols
         self.splitByRow = splitByRow
 
+        if cornerRadii is not None:
+            self._setCornerRadii(cornerRadii)
+
         if style:
             self.setStyle(style)
 
@@ -311,6 +357,7 @@
                 minRowHeights = minRowHeights+(nrows-lmrh)*minRowHeights.__class__((0,))
         self._minRowHeights = minRowHeights
 
+
     def __repr__(self):
         "incomplete, but better than nothing"
         r = getattr(self,'_rowHeights','[unknown]')
@@ -1098,6 +1145,9 @@
             assert len(cmd) == 10
 
             self._linecmds.append(tuple(cmd))
+        elif cmd[0]=="ROUNDEDCORNERS":
+            if not hasattr(self,"_cornerRadii"):
+                self._setCornerRadii(cmd[1])
         else:
             (op, (sc, sr), (ec, er)), values = cmd[:3] , cmd[3:]
             if sr in ('splitfirst','splitlast'):
@@ -1111,25 +1161,48 @@
 
     def _drawLines(self):
         ccap, cdash, cjoin = None, None, None
-        self.canv.saveState()
-        for op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space in self._linecmds:
-            if isinstance(sr,strTypes) and sr.startswith('split'): continue
-            if cap!=None and ccap!=cap:
-                self.canv.setLineCap(cap)
-                ccap = cap
-            if dash is None or dash == []:
-                if cdash is not None:
-                    self.canv.setDash()
-                    cdash = None
-            elif dash != cdash:
-                self.canv.setDash(dash)
-                cdash = dash
-            if join is not None and cjoin!=join:
-                self.canv.setLineJoin(join)
-                cjoin = join
-            sc, ec, sr, er = self.normCellRange(sc,ec,sr,er)
-            getattr(self,_LineOpMap.get(op, '_drawUnknown' ))( (sc, sr), (ec, er), weight, color, count, space)
-        self.canv.restoreState()
+        canv = self.canv
+        canv.saveState()
+
+        rrd = self._roundingRectDef
+        if rrd: #we are collection some lines
+            SL = rrd.SL
+            SL[:] = []  #empty saved lines list
+            ocanvline = canv.line
+            aSL = SL.append
+            def rcCanvLine(xs, ys, xe, ye):
+                if  (
+                    (xs==xe and (xs>=rrd.x1 or xs<=rrd.x0)) #vertical line that needs to be saved
+                    or
+                    (ys==ye and (ys>=rrd.y1 or ys<=rrd.y0)) #horizontal line that needs to be saved
+                    ):
+                    aSL(RoundingRectLine(xs,ys,xe,ye,weight,color,cap,dash,join))
+                else:
+                    ocanvline(xs,ys,xe,ye)
+            canv.line = rcCanvLine
+
+        try:
+            for op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space in self._linecmds:
+                if isinstance(sr,strTypes) and sr.startswith('split'): continue
+                if cap!=None and ccap!=cap:
+                    canv.setLineCap(cap)
+                    ccap = cap
+                if dash is None or dash == []:
+                    if cdash is not None:
+                        canv.setDash()
+                        cdash = None
+                elif dash != cdash:
+                    canv.setDash(dash)
+                    cdash = dash
+                if join is not None and cjoin!=join:
+                    canv.setLineJoin(join)
+                    cjoin = join
+                sc, ec, sr, er = self.normCellRange(sc,ec,sr,er)
+                getattr(self,_LineOpMap.get(op, '_drawUnknown' ))( (sc, sr), (ec, er), weight, color, count, space)
+        finally:
+            if rrd: 
+                canv.line = ocanvline
+        canv.restoreState()
         self._curcolor = None
 
     def _drawUnknown(self,  start, end, weight, color, count, space):
@@ -1313,12 +1386,14 @@
             splitH = self._rowHeights
         else:
             splitH = self._argH
+        cornerRadii = getattr(self,'_cornerRadii',None)
         R0 = self.__class__( data[:n], colWidths=self._colWidths, rowHeights=splitH[:n],
                 repeatRows=repeatRows, repeatCols=repeatCols,
                 splitByRow=splitByRow, normalizedData=1, cellStyles=self._cellStyles[:n],
                 ident=ident,
                 spaceBefore=getattr(self,'spaceBefore',None),
-                longTableOptimize=lto)
+                longTableOptimize=lto,
+                cornerRadii=cornerRadii[:2] if cornerRadii else None)
 
         nrows = self._nrows
         ncols = self._ncols
@@ -1399,6 +1474,7 @@
                     ident=ident,
                     spaceAfter=getattr(self,'spaceAfter',None),
                     longTableOptimize=lto,
+                    cornerRadii = cornerRadii,
                     )
             R1._cr_1_1(n,nrows,repeatRows,A) #linecommands
             R1._cr_1_1(n,nrows,repeatRows,self._bkgrndcmds,_srflMode=True)
@@ -1412,6 +1488,7 @@
                     ident=ident,
                     spaceAfter=getattr(self,'spaceAfter',None),
                     longTableOptimize=lto,
+                    cornerRadii = ([0,0] + cornerRadii[2:]) if cornerRadii else None,
                     )
             R1._cr_1_0(n,A)
             R1._cr_1_0(n,self._bkgrndcmds,_srflMode=True)
@@ -1470,7 +1547,117 @@
         else:
             raise NotImplementedError
 
+    def _makeRoundedCornersClip(self, FUZZ=rl_config._FUZZ):
+        self._roundingRectDef = None
+        cornerRadii = getattr(self,'_cornerRadii',None)
+        if not cornerRadii or max(cornerRadii)<=FUZZ: return
+        nrows = self._nrows
+        ncols = self._ncols
+        ar = [min(self._rowHeights[i],self._colWidths[j],cornerRadii[k]) for 
+                k,(i,j) in enumerate((
+                    (0,0),
+                    (0,ncols-1),
+                    (nrows-1,0),
+                    (nrows-1, ncols-1),
+                    ))]
+        rp = self._rowpositions
+        cp = self._colpositions
+
+        x0 = cp[0]
+        y0 = rp[nrows]
+        x1 = cp[ncols]
+        y1 = rp[0]
+        w = x1 - x0
+        h = y1 - y0
+        self._roundingRectDef = RoundingRectDef(x0, y0, w, h, x1, y1, ar, [])
+        P = self.canv.beginPath()
+        P.roundRect(x0, y0, w, h, ar)
+        c = self.canv
+        c.addLiteral('%begin table rect clip')
+        c.clipPath(P,stroke=0)
+        c.addLiteral('%end table rect clip')
+
+    def _restoreRoundingObscuredLines(self):
+        x0, y0, w, h, x1, y1, ar, SL = self._roundingRectDef
+        if not SL: return
+        canv = self.canv
+        canv.saveState()
+        ccap, cdash, cjoin = None, None, None
+        line = canv.line
+        cornerRadii = self._cornerRadii
+        for (xs,ys,xe,ye,weight,color,cap,dash,join) in SL:
+            if cap!=None and ccap!=cap:
+                canv.setLineCap(cap)
+                ccap = cap
+            if dash is None or dash == []:
+                if cdash is not None:
+                    canv.setDash()
+                    cdash = None
+            elif dash != cdash:
+                canv.setDash(dash)
+                cdash = dash
+            if join is not None and cjoin!=join:
+                canv.setLineJoin(join)
+                cjoin = join
+            self._prepLine(weight, color)
+            if ys==ye:
+                #horizontal line
+                if ys>y1 or ys<y0:
+                    line(xs,ys,xe,ye)   #simple line that's outside the clip
+                    continue
+                #which corners are involved
+                if ys==y0:
+                    ypos = 'bottom'
+                    r0 = ar[2]
+                    r1 = ar[3]
+                else: #ys==y1
+                    ypos = 'top'
+                    r0 = ar[0]
+                    r1 = ar[1]
+                if xs>=x0+r0 and xe<=x1-r1:
+                    line(xs,ys,xe,ye)   #simple line with no rounding
+                    continue
+                #we have some rounding so we must use a path
+                c0 = _quadrantDef('left',ypos,(xs,ys), r0, kind=2, direction='left-right') if xs<x0+r0 else None
+                c1 = _quadrantDef('right',ypos,(xe,ye), r1, kind=1, direction='left-right') if xe>x1-r1 else None
+            else:
+                #vertical line
+                if xs>x1 or xs<x0:
+                    line(xs,ys,xe,ye)   #simple line that's outside the clip
+                    continue
+                #which corners are involved
+                if xs==x0:
+                    xpos = 'left'
+                    r0 = ar[2]
+                    r1 = ar[0]
+                else: #xs==x1
+                    xpos = 'right'
+                    r0 = ar[3]
+                    r1 = ar[1]
+                if ys>=y0+r0 and ye<=y1-r1:
+                    line(xs,ys,xe,ye)   #simple line with no rounding
+                    continue
+                #we have some rounding so we must use a path
+                c0 = _quadrantDef(xpos,'bottom',(xs,ys), r0, kind=2, direction='bottom-top') if ys<y0+r0 else None
+                c1 = _quadrantDef(xpos,'top',(xe,ye), r1, kind=1, direction='bottom-top') if ye>y1-r1 else None
+            P = canv.beginPath()
+            if c0:
+                P.moveTo(*c0[0])
+                P.curveTo(c0[1][0],c0[1][1],c0[2][0],c0[2][1], c0[3][0],c0[3][1])
+            else:
+                P.moveTo(xs,ys)
+            if not c1:
+                P.lineTo(xe,ye)
+            else:
+                P.lineTo(*c1[0])
+                P.curveTo(c1[1][0],c1[1][1],c1[2][0],c1[2][1], c1[3][0],c1[3][1])
+            canv.drawPath(P, stroke=1, fill=0)
+        canv.restoreState()
+
     def draw(self):
+        c = self.canv
+        c.saveState()
+        self._makeRoundedCornersClip()
         self._curweight = self._curcolor = self._curcellstyle = None
         self._drawBkgrnd()
         if not self._spanCmds:
@@ -1490,6 +1677,9 @@
                         cellstyle = self._cellStyles[rowNo][colNo]
                         self._drawCell(cellval, cellstyle, (x, y), (width, height))
         self._drawLines()
+        c.restoreState()
+        if self._roundingRectDef:
+            self._restoreRoundingObscuredLines()
 
     def _drawBkgrnd(self):
         nrows = self._nrows
@@ -1656,6 +1846,12 @@
             #external hyperlink
             self.canv.linkRect("", cellstyle.destination, Rect=(colpos, rowpos, colpos + colwidth, rowpos + rowheight), relative=1)
 
+    def _setCornerRadii(self, cornerRadii):
+        if isListOfNumbersOrNone(cornerRadii):
+            self._cornerRadii = None if not cornerRadii else list(cornerRadii) + (max(4-len(cornerRadii),0)*[0])
+        else:
+            raise ValueError('cornerRadii should be None or a list/tuple of numeric radii')
+
 _LineOpMap = {  'GRID':'_drawGrid',
                 'BOX':'_drawBox',
                 'OUTLINE':'_drawBox',
--- a/tests/test_pdfgen_general.py	Sun Dec 27 11:36:08 2020 +0000
+++ b/tests/test_pdfgen_general.py	Fri Jan 01 17:42:23 2021 +0000
@@ -463,6 +463,12 @@
     t.textLine('canvas.rect(x, y, width, height) - x,y is lower left')
 
     c.roundRect(inch,6.25*inch,2*inch,0.6*inch,0.1*inch)
+    c.saveState()
+    c.setStrokeColor(red)
+    c.roundRect(inch*1.1,6.35*inch,1.8*inch,0.4*inch,[0.1*inch,0,0.09*inch,0])
+    c.setStrokeColor(green)
+    c.roundRect(inch*1.15,6.40*inch,1.7*inch,0.3*inch,[0,0.1*inch,0,0.09*inch])
+    c.restoreState()
     t.setTextOrigin(4*inch, 6.55*inch)
     t.textLine('canvas.roundRect(x,y,width,height,radius)')
 
--- a/tests/test_platypus_tables.py	Sun Dec 27 11:36:08 2020 +0000
+++ b/tests/test_platypus_tables.py	Fri Jan 01 17:42:23 2021 +0000
@@ -1066,6 +1066,54 @@
         self.assertEqual(len(T[1]._bkgrndcmds),3)
         doc.build(story)
 
+    def test5(self):
+        '''test rounded corners'''
+        story = []
+        story_add = story.append
+        ts_tables = [
+                 ('BACKGROUND',(0,0),(-1,0),colors.pink),
+                 ('BACKGROUND',(0,1),(-1,1),colors.lightblue),
+                 ('ROWBACKGROUNDS',(0,2),(-1,-1),(colors.lightgrey,None)),
+                 ('TEXTCOLOR',(0,0),(-1,0),colors.green),
+                 ('TEXTCOLOR',(0,1),(-1,1),colors.red),
+                 ('LINEABOVE', (0,0), (-1,0), 1, colors.purple),
+                 ('LINEBELOW', (0,0), (-1,0), 2, colors.purple),
+                 ('LINEABOVE', (0,1), (-1,1), 1, colors.orange),
+                 ('LINEBELOW', (0,1), (-1,1), 2, colors.orange),
+                 ('LINEBEFORE', (0,0), (0,-1), 1, colors.red),
+                 ('LINEAFTER', (-1,0), (-1,-1), 1, colors.blue),
+                 ('LINEBELOW', (0,-1), (-1,-1), 1, colors.green),
+                 ('FONT', (2,2), (5,8), 'Times-Bold'),
+                 ]
+        data = self.data34
+        from reportlab.platypus import Paragraph, Table, SimpleDocTemplate, PageBreak
+        from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
+        styleSheet = getSampleStyleSheet()
+        bodyText = styleSheet['BodyText']
+
+        story_add(Paragraph('The whole table',bodyText))
+        t = Table(data, style=ts_tables, repeatRows=2, cornerRadii=[5,6,4,3])
+        story_add(t)
+        t = Table(data, style=ts_tables, repeatRows=2, cornerRadii=[5,6,4,5])
+        T = t.split(4*72,90)
+        story_add(Paragraph('The split table part 0',bodyText))
+        story_add(T[0])
+        story_add(Paragraph('The split table part 1',bodyText))
+        story_add(T[1])
+        story_add(PageBreak())
+        ts2 = ts_tables+[('ROUNDEDCORNERS',[5,6,4,3])]
+        story_add(Paragraph('Rounded corners via style',bodyText))
+        t = Table(data, style=ts2, repeatRows=2)
+        story_add(t)
+        t = Table(data, style=ts2, repeatRows=2)
+        T = t.split(4*72,90)
+        story_add(Paragraph('The split table part 0',bodyText))
+        story_add(T[0])
+        story_add(Paragraph('The split table part 1',bodyText))
+        story_add(T[1])
+        doc = SimpleDocTemplate(outputfile('test_platypus_tables_rounded_corners.pdf'), showBoundary=0)
+        doc.build(story)
+
 def makeSuite():
     return makeSuiteForClasses(TablesTestCase)