Re: Patch to support tables with oversize cells

Lennart Regebro via reportlab-users <[email protected]> Tue, 1 Feb 2022 17:34:41 +0100
Newsgroups gmane.comp.python.reportlab.user
Message-ID <CAHT-kBcOxkrLfyzDkR11QDC5aW6rw0W3h3RxAB5bKdCi_HJ98Q@mail.gmail.com>
OK, I got a small bit wrong in the logic of when to split in each way,
fixed now, new patch attached.

I guess rml should support a splitInRow attribute on tables as well, but I
can't figure out how that works. The only mention I can find of splitByRow
in rlextra is in the rml.dtd. If I add splitInRow there, the parser doesn't
complain, but it doesn't get passed to the table...

On Fri, Jan 28, 2022 at 1:44 PM Robin Becker <[email protected]> wrote:

> On 13/01/2022 13:24, Lennart Regebro wrote:
> > On Thu, Jan 13, 2022 at 11:33 AM Robin Becker <[email protected]>
> wrote:
> >
> >> Hi Lennart,
> >>
> >> I looked at this problem recently and concluded I didn't have enough
> >> stamina to work through all the problems. If you
> >> have then I congratulate you.
> >>
> >
> > It did take a long time. I *hope* I found and fixed all the problems.
> >
> Hi Lennart,
>
> I tried applying this patch and running through our usual comparison tests.
>
> I see immediately that a downstream failure test fails (ie a pdf was
> produced when it should have errored out). We have
> tests in rml which can use a fairly complex backtrack to determine exactly
> which but of xml produced the error.
>
> Looked in the patch and see that it defaults to splitInRow=1. I changed
> that to 0 and expected it would stop any change
> in behaviour. However, I am still seeing a success when I expect a failure.
>
> I made a zip containing the differences I see in before and after pdf for
> the reportlab tests;  see
>
> https://www.reportlab.com/ftp/lennart-oversize-cells-diffs-20220128-0.zip
>
> for the page level differences. These were made with the default for
> splitInRow=0.
>
> The rml file that should fail is
>
> #######################################################
> <?xml version="1.0" encoding="utf-8" standalone="no" ?>
> <!DOCTYPE document SYSTEM "rml_1_0.dtd">
> <!--expect:rowHeights='150cm'> (Table) @ 12:49 - 14:15-->
> <document filename="test_054_blockTable_run_error.pdf">
>         <template pageSize="(595, 842)" leftMargin="72" showBoundary="1">
>                 <pageTemplate id="main">
>                         <frame id="second" x1="35" y1="45" width="525"
> height="590"/>
>                 </pageTemplate>
>         </template>
>         <stylesheet/>
>         <story>
>                 <blockTable colWidths="3cm" rowHeights="150cm">
>                         <tr><td/></tr>
>                 </blockTable>
>         </story>
> </document>
> #######################################################
>
> which is a row of height 150cm inside frame of height 590pts. It should
> fail with a layout error. I can probably
> recreate in simple reportlab, but as we have other unexpected differences
> in simple reportlab I guess those need
> correction first.
> --
> Robin Becker
>
splitInRow.diff (text/x-patch, 27.3 KB)
diff --git a/src/reportlab/platypus/tables.py b/src/reportlab/platypus/tables.py
index 2b1003c..4ffb225 100755
--- a/src/reportlab/platypus/tables.py
+++ b/src/reportlab/platypus/tables.py
@@ -60,8 +60,9 @@ class CellStyle(PropertySet):
             parent.copy(self)
     def copy(self, result=None):
         if result is None:
-            result = CellStyle()
+            result = CellStyle(self.name)
         for name in dir(self):
+            if name.startswith('_'): continue
             setattr(result, name, getattr(self, name))
         return result
 
@@ -251,7 +252,7 @@ RoundingRectLine = namedtuple('RoundingRectLine','xs ys xe ye weight color cap d
 
 class Table(Flowable):
     def __init__(self, data, colWidths=None, rowHeights=None, style=None,
-                repeatRows=0, repeatCols=0, splitByRow=1, emptyTableAction=None, ident=None,
+                repeatRows=0, repeatCols=0, splitByRow=1, splitInRow=1, emptyTableAction=None, ident=None,
                 hAlign=None,vAlign=None, normalizedData=0, cellStyles=None, rowSplitRange=None,
                 spaceBefore=None,spaceAfter=None, longTableOptimize=None, minRowHeights=None,
                 cornerRadii=__UNSET__, #or [topLeft, topRight, bottomLeft bottomRight]
@@ -336,6 +337,7 @@ class Table(Flowable):
         self.repeatRows = repeatRows
         self.repeatCols = repeatCols
         self.splitByRow = splitByRow
+        self.splitInRow = splitInRow
 
         if style:
             self.setStyle(style)
@@ -464,6 +466,10 @@ class Table(Flowable):
         w = 0
         canv = getattr(self,'canv',None)
         sb0 = None
+        if isinstance(V, str):
+            vw = self._elementWidth(V, s)
+            vh = len(V.split('\n'))*s.fontsize*1.2
+            return max(w, vw), vh
         for v in V:
             vw, vh = v.wrapOn(canv, aW, aH)
             sb = v.getSpaceBefore()
@@ -1199,7 +1205,7 @@ class Table(Flowable):
                 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: 
+            if rrd:
                 canv.line = ocanvline
         canv.restoreState()
         self._curcolor = None
@@ -1356,44 +1362,97 @@ class Table(Flowable):
             if er>=n: er -= n
             self._addCommand((c[0],)+((sc, sr), (ec, er))+tuple(c[3:]))
 
-    def _splitRows(self,availHeight):
-        n=self._getFirstPossibleSplitRowPosition(availHeight)
-        repeatRows = self.repeatRows
-        if n<= (repeatRows if isinstance(repeatRows,int) else (max(repeatRows)+1)): return []
-        lim = len(self._rowHeights)
-        if n==lim: return [self]
+    def _splitCell(self, value, style, oldHeight, newHeight, width):
+        # Content height of the new top row
+        height0 = newHeight - style.topPadding
+        # Content height of the new bottom row
+        height1 = oldHeight - (style.topPadding + newHeight)
 
-        lo = self._rowSplitRange
-        if lo:
-            lo, hi = lo
-            if lo<0: lo += lim
-            if hi<0: hi += lim
-            if n>hi:
-                return self._splitRows(availHeight - sum(self._rowHeights[hi:n]))
-            elif n<lo:
+        if isinstance(value, (tuple, list)):
+            newCellContent = []
+            postponedContent = []
+            split = False
+            cellHeight = self._listCellGeom(value, width, style)[1]
+
+            if style.valign == "MIDDLE":
+                usedHeight = (oldHeight - cellHeight) / 2
+            else:
+                usedHeight = 0
+
+            for flowable in value:
+                if split:
+                    if flowable.height <= height1:
+                        postponedContent.append(flowable)
+                        # Shrink the available height:
+                        height1 -= flowable.height
+                    else:
+                        # The content doesn't fit after the split:
+                        return []
+                elif usedHeight + flowable.height <= height0:
+                    newCellContent.append(flowable)
+                    usedHeight += flowable.height
+                else:
+                    # This is where we need to split
+                    splits = flowable.split(width, height0-usedHeight)
+                    if splits:
+                        newCellContent.append(splits[0])
+                        postponedContent.append(splits[1])
+                    else:
+                        # We couldn't split this flowable at the desired
+                        # point. If we already has added previous paragraphs
+                        # to the content, just add everything after the split.
+                        # Also try adding it after the split if valign isn't TOP
+                        if newCellContent or style.valign != "TOP":
+                            if flowable.height <= height1:
+                                postponedContent.append(flowable)
+                                # Shrink the available height:
+                                height1 -= flowable.height
+                            else:
+                                # The content doesn't fit after the split:
+                                return []
+                        else:
+                            # We could not split this, so we fail:
+                            return []
+
+                    split = True
+
+            return (tuple(newCellContent), tuple(postponedContent))
+
+        elif isinstance(value, str):
+            rows = value.split("\n")
+            lineHeight = 1.2 * style.fontsize
+            contentHeight = (style.leading or lineHeight) * len(rows)
+            if style.valign == "TOP" and contentHeight <= height0:
+                # This fits in the first cell, all is good
+                return (value, '')
+            elif style.valign == "BOTTOM" and contentHeight <= height1:
+                # This fits in the second cell, all is good
+                return ('', value)
+            elif style.valign == "MIDDLE":
+                # Put it in the largest cell:
+                if height1 > height0:
+                    return ('', value)
+                else:
+                    return (value, '')
+
+            elif len(rows) < 2:
+                # It doesn't fit, and there's nothing to split: Fail
                 return []
+            # We need to split this, and there are multiple lines, so we can
+            if style.valign == "TOP":
+                splitPoint = height0 // lineHeight
+            elif style.valign == "BOTTOM":
+                splitPoint = len(rows) - (height1 // lineHeight)
+            else:  # MID
+                splitPoint = (height0 - height1 + contentHeight) // (2 * lineHeight)
 
-        repeatCols = self.repeatCols
-        splitByRow = self.splitByRow
-        data = self._cellvalues
+            splitPoint = int(splitPoint)
+            return ('\n'.join(rows[:splitPoint]), '\n'.join(rows[splitPoint:]))
 
-        #we're going to split into two superRows
-        ident = self.ident
-        if ident: ident = IdentStr(ident)
-        lto = self._longTableOptimize
-        if lto:
-            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,
-                cornerRadii=cornerRadii[:2] if cornerRadii else None)
+        # No content
+        return ('', '')
 
+    def _splitLineCmds(self, n, doInRowSplit=0):
         nrows = self._nrows
         ncols = self._ncols
         #copy the commands
@@ -1414,16 +1473,21 @@ class Table(Flowable):
             if er < 0: er += nrows
 
             if op in ('BOX','OUTLINE','GRID'):
-                if sr<n and er>=n:
+                if (sr<n and er>=n) or (doInRowSplit and sr==n):
                     # we have to split the BOX
                     A.append(('LINEABOVE',(sc,sr), (ec,sr), weight, color, cap, dash, join, count, space))
                     A.append(('LINEBEFORE',(sc,sr), (sc,er), weight, color, cap, dash, join, count, space))
                     A.append(('LINEAFTER',(ec,sr), (ec,er), weight, color, cap, dash, join, count, space))
                     A.append(('LINEBELOW',(sc,er), (ec,er), weight, color, cap, dash, join, count, space))
                     if op=='GRID':
-                        A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space))
-                        A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space))
-                        A.append(('INNERGRID',(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
+                        if doInRowSplit:
+                            A.append(('INNERGRID',(sc,sr), (ec,n), weight, color, cap, dash, join, count, space))
+                            A.append(('INNERGRID',(sc,n+1), (ec,er), weight, color, cap, dash, join, count, space))
+                            #A.append(('LINEBELOW',(sc,n), (ec,n), weight, color, cap, dash, join, count, space))
+                        else:
+                            A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space))
+                            A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space))
+                            A.append(('INNERGRID',(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
                 else:
                     A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
             elif op == 'INNERGRID':
@@ -1442,11 +1506,180 @@ class Table(Flowable):
             else:
                 A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
 
+        return A
+
+    def _splitRows(self,availHeight,doInRowSplit=0):
+        # Get the split position. if we split between rows (doInRowSplit=0),
+        # then n will be the first row after the split. If we split a row,
+        # then n is the row we split in two.
+        n=self._getFirstPossibleSplitRowPosition(availHeight)
+
+        # We can't split before or in the repeatRows/headers
+        repeatRows = self.repeatRows
+        maxrepeat = repeatRows if isinstance(repeatRows,int) else max(repeatRows)+1
+        if doInRowSplit and n<maxrepeat or not doInRowSplit and n<=maxrepeat:
+            return []
+
+        # If the whole table fits, return it
+        lim = len(self._rowHeights)
+        if n==lim: return [self]
+
+        lo = self._rowSplitRange
+        if lo:
+            lo, hi = lo
+            if lo<0: lo += lim
+            if hi<0: hi += lim
+            if n>hi:
+                return self._splitRows(availHeight - sum(self._rowHeights[hi:n]), doInRowSplit=doInRowSplit)
+            elif n<lo:
+                return []
+
+        repeatCols = self.repeatCols
+        splitByRow = self.splitByRow
+        splitInRow = self.splitInRow
+        data = self._cellvalues
+
+        if not doInRowSplit:
+            T = self
+        else:
+            # We are splitting the n row into two, if possible.
+            # We can't split if the available height is less than the minimum set:
+            if self._minRowHeights and availHeight < self._minRowHeights[n]:
+                return []
+
+            usedHeights = sum(self._rowHeights[:n])
+
+            cellvalues = self._cellvalues[n]
+            cellStyles = self._cellStyles[n]
+            cellWidths = self._colWidths
+            curRowHeight = self._rowHeights[n]
+
+            # First find the min/max split point
+            minSplit = 0  # Counted from top
+            maxSplit = 0  # Counted from bottom
+            maxHeight = 0
+
+            for (value, style, width) in zip(cellvalues, cellStyles, cellWidths):
+
+                if isinstance(value, (tuple, list)):
+                    # A sequence of flowables:
+                    w, height = self._listCellGeom(value, width, style)
+                    height += style.topPadding + style.bottomPadding
+                    if height > maxHeight:
+                        maxHeight = height
+                elif isinstance(value, str):
+                    rows = value.split("\n")
+                    lineHeight = 1.2 * style.fontsize
+                    height = lineHeight * len(rows) + style.topPadding + style.bottomPadding
+
+                    # Make sure we don't try to split in the middle of the first or last line
+                    minSplit = max(minSplit, lineHeight + style.topPadding)
+                    maxSplit = max(maxSplit, lineHeight + style.bottomPadding)
+
+                    if height > maxHeight:
+                        maxHeight = height
+
+            if minSplit + maxSplit > curRowHeight:
+                return []
+            if minSplit > (availHeight - usedHeights):  # Fail
+                return []
+
+            # This is where we split the row:
+            splitPoint = min(availHeight - usedHeights, maxHeight - maxSplit)
+
+            R0 = []  # Top half of the row
+            R0Height = 0  # Minimum height
+            R1 = []  # Bottom half of the row
+            R1Height = 0  # Minimum height
+            R1Styles = []
+            for (value, style, width) in zip(cellvalues, cellStyles, cellWidths):
+                v = self._splitCell(value, style, curRowHeight, splitPoint, width)
+                if not v:
+                    # Splitting the table failed
+                    return []
+
+                newStyle = style.copy()
+                if style.valign == "MIDDLE":
+                    # Adjust margins
+                    if v[0] and v[1]:
+                        # We split the content, so fix up the valign:
+                        style.valign = "BOTTOM"
+                        newStyle.valign = "TOP"
+                    else:
+                        # Adjust the margins to push it towards the true middle
+                        h = self._listCellGeom(v[0] or v[1], width, style)[1]
+                        margin = (curRowHeight - h) / 2
+                        if v[0]:
+                            style.topPadding += margin
+                        elif v[1]:
+                            newStyle.bottomPadding += margin
+                R0.append(v[0])
+                R1.append(v[1])
+                h0 = self._listCellGeom(v[0], width, style)[1] + style.topPadding + style.bottomPadding
+                R0Height = max(R0Height, h0)
+                h1 = self._listCellGeom(v[1], width, style)[1] + style.topPadding + style.bottomPadding
+                R1Height = max(R1Height, h1)
+                R1Styles.append(newStyle)
+
+            # Make a new table with the row split into two:
+            usedHeight = min(splitPoint, R0Height)
+            newRowHeight = max(R1Height, self._rowHeights[n] - usedHeight)
+            newRowHeights = self._rowHeights[:]
+            newRowHeights.insert(n + 1, newRowHeight)
+            newRowHeights[n] = usedHeight
+            newCellStyles = self._cellStyles[:]
+            newCellStyles.insert(n + 1, R1Styles)
+
+            data = data[:n] + [R0] + [R1] + data[n+1:]
+
+            T = self.__class__( data, colWidths=self._colWidths,
+                rowHeights=newRowHeights, repeatRows=self.repeatRows,
+                repeatCols=self.repeatCols, splitByRow=self.splitByRow,
+                splitInRow=self.splitInRow, normalizedData=1,
+                cellStyles=newCellStyles, ident=self.ident,
+                spaceBefore=getattr(self,'spaceBefore',None),
+                longTableOptimize=self._longTableOptimize,
+                cornerRadii=getattr(self,'_cornerRadii',None))
+
+            T._linecmds = self._linecmds
+            T._linecmds = T._splitLineCmds(n, doInRowSplit=1)
+            n = n + 1
+
+        #we're going to split into two superRows
+        ident = self.ident
+        if ident: ident = IdentStr(ident)
+        lto = T._longTableOptimize
+        if lto:
+            splitH = T._rowHeights
+        else:
+            splitH = T._argH
+
+        if doInRowSplit:
+            spaceAfter = None
+        else:
+            spaceAfter = availHeight - sum(splitH[:n])
+
+
+        cornerRadii = getattr(self,'_cornerRadii',None)
+        R0 = self.__class__( data[:n], colWidths=T._colWidths, rowHeights=splitH[:n],
+                repeatRows=repeatRows, repeatCols=repeatCols, splitByRow=splitByRow,
+                splitInRow=splitInRow, normalizedData=1, cellStyles=T._cellStyles[:n],
+                ident=ident,
+                spaceBefore=getattr(self,'spaceBefore',None),
+                spaceAfter=spaceAfter,
+                longTableOptimize=lto,
+                cornerRadii=cornerRadii[:2] if cornerRadii else None)
+
+        nrows = T._nrows
+        ncols = T._ncols
+
+        A = T._splitLineCmds(n, doInRowSplit=doInRowSplit)
+
         R0._cr_0(n,A,nrows)
-        R0._cr_0(n,self._bkgrndcmds,nrows,_srflMode=True)
-        R0._cr_0(n,self._spanCmds,nrows)
-        R0._cr_0(n,self._nosplitCmds,nrows)
-        for c in self._srflcmds:
+        R0._cr_0(n,T._bkgrndcmds,nrows,_srflMode=True)
+        R0._cr_0(n,T._spanCmds,nrows)
+        R0._cr_0(n,T._nosplitCmds,nrows)
+        for c in T._srflcmds:
             R0._addCommand(c)
             if c[1][1]!='splitlast': continue
             (sc,sr), (ec,er) = c[1:3]
@@ -1457,50 +1690,50 @@ class Table(Flowable):
             if isinstance(repeatRows,int):
                 iRows = data[:repeatRows]
                 iRowH = splitH[:repeatRows]
-                iCS = self._cellStyles[:repeatRows]
+                iCS = T._cellStyles[:repeatRows]
                 repeatRows = list(range(repeatRows))
             else:
                 #we have a list of repeated rows eg (1,3)
                 repeatRows = list(sorted(repeatRows))
                 iRows = [data[i] for i in repeatRows]
                 iRowH = [splitH[i] for i in repeatRows]
-                iCS = [self._cellStyles[i] for i in repeatRows]
-            R1 = self.__class__(iRows+data[n:],colWidths=self._colWidths,
+                iCS = [T._cellStyles[i] for i in repeatRows]
+            R1 = self.__class__(iRows+data[n:],colWidths=T._colWidths,
                     rowHeights=iRowH+splitH[n:],
                     repeatRows=len(repeatRows), repeatCols=repeatCols,
                     splitByRow=splitByRow, normalizedData=1,
-                    cellStyles=iCS+self._cellStyles[n:],
+                    cellStyles=iCS+T._cellStyles[n:],
                     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)
-            R1._cr_1_1(n,nrows,repeatRows,self._spanCmds)
-            R1._cr_1_1(n,nrows,repeatRows,self._nosplitCmds)
+            R1._cr_1_1(n,nrows,repeatRows,T._bkgrndcmds,_srflMode=True)
+            R1._cr_1_1(n,nrows,repeatRows,T._spanCmds)
+            R1._cr_1_1(n,nrows,repeatRows,T._nosplitCmds)
         else:
             #R1 = slelf.__class__(data[n:], self._argW, self._argH[n:],
-            R1 = self.__class__(data[n:], colWidths=self._colWidths, rowHeights=splitH[n:],
+            R1 = self.__class__(data[n:], colWidths=T._colWidths, rowHeights=splitH[n:],
                     repeatRows=repeatRows, repeatCols=repeatCols,
-                    splitByRow=splitByRow, normalizedData=1, cellStyles=self._cellStyles[n:],
+                    splitByRow=splitByRow, normalizedData=1, cellStyles=T._cellStyles[n:],
                     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)
-            R1._cr_1_0(n,self._spanCmds)
-            R1._cr_1_0(n,self._nosplitCmds)
-        for c in self._srflcmds:
+            R1._cr_1_0(n,T._bkgrndcmds,_srflMode=True)
+            R1._cr_1_0(n,T._spanCmds)
+            R1._cr_1_0(n,T._nosplitCmds)
+        for c in T._srflcmds:
             R1._addCommand(c)
             if c[1][1]!='splitfirst': continue
             (sc,sr), (ec,er) = c[1:3]
             R1._addCommand((c[0],)+((sc, 0), (ec, 0))+tuple(c[3:]))
 
-        R0.hAlign = R1.hAlign = self.hAlign
-        R0.vAlign = R1.vAlign = self.vAlign
+        R0.hAlign = R1.hAlign = T.hAlign
+        R0.vAlign = R1.vAlign = T.vAlign
         self.onSplit(R0)
         self.onSplit(R1)
         return [R0,R1]
@@ -1521,6 +1754,7 @@ class Table(Flowable):
     _getRowImpossible=staticmethod(_getRowImpossible)
 
     def _getFirstPossibleSplitRowPosition(self,availHeight):
+        # Returns the LAST possible split row position
         impossible={}
         if self._spanCmds:
             self._getRowImpossible(impossible,self._rowSpanCells,self._spanRanges)
@@ -1540,11 +1774,23 @@ class Table(Flowable):
 
     def split(self, availWidth, availHeight):
         self._calc(availWidth, availHeight)
-        if self.splitByRow:
+        if self.splitByRow or self.splitInRow:
             if not rl_config.allowTableBoundsErrors and self._width>availWidth: return []
-            return self._splitRows(availHeight)
-        else:
-            raise NotImplementedError
+
+            # If self.splitByRow is true, first try with doInRowSplit as false.
+            # Otherwise, first try with doInRowSplit as true
+            result = self._splitRows(availHeight, doInRowSplit=not self.splitByRow)
+            if result:
+                # That worked, return that:
+                return result
+
+            # The first attempt did NOT succeed, now try with the flag flipped
+            # (unless self.splitInRow is false)
+            if self.splitInRow:
+                return self._splitRows(availHeight, doInRowSplit=self.splitByRow)
+
+        # We can't split this table in any way, raise an error:
+        return []
 
     def _makeRoundedCornersClip(self, FUZZ=rl_config._FUZZ):
         self._roundingRectDef = None
@@ -1552,7 +1798,7 @@ class Table(Flowable):
         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 
+        ar = [min(self._rowHeights[i],self._colWidths[j],cornerRadii[k]) for
                 k,(i,j) in enumerate((
                     (0,0),
                     (0,ncols-1),
diff --git a/tests/test_table_layout.py b/tests/test_table_layout.py
index edfe436..b019f1f 100644
--- a/tests/test_table_layout.py
+++ b/tests/test_table_layout.py
@@ -403,6 +403,120 @@ class TableTestCase(unittest.TestCase):
         lst.append(Paragraph("""This code does not yet handle spans well.""",
         styNormal))
 
+        lst.append(PageBreak())
+
+        lst.append(Paragraph("""Oversized cells""", styleSheet['Heading1']))
+
+        lst.append(Paragraph("""In some cases cells with flowables can end up
+        being larger than a page. In that case, we need to split the page.
+        The splitInRow attribute allows that, it's by default 0.""",
+        styNormal))
+
+        lst.append(Paragraph("""Here is a table, with splitByRow=1 and
+        splitInRow=0. It splits between the two rows.""",
+        styNormal))
+
+        ministy = TableStyle([
+            ('GRID', (0,0), (-1,-1), 1.0, colors.black),
+            ('VALIGN', (0,1), (1,1), 'BOTTOM'),
+            ('VALIGN', (1,1), (2,1), 'MIDDLE'),
+            ('VALIGN', (2,1), (3,1), 'TOP'),
+            ('VALIGN', (3,1), (4,1), 'BOTTOM'),
+            ('VALIGN', (4,1), (5,1), 'MIDDLE'),
+            ('VALIGN', (5,1), (6,1), 'TOP'),
+            ])
+        cell1 = [Paragraph(
+            """This is a very tall cell to make a tall row for splitting.""",
+            styNormal)]
+        cell2 = [Paragraph("This cell has two flowables.", styNormal),
+            Paragraph("And valign=MIDDLE.", styNormal)]
+        cell3 = [Paragraph("Paragraph with valign=TOP", styNormal)]
+
+        tableData = [
+            ['Row 1', 'Two rows:\nSo there', 'is a', 'place', 'to split', 'the table'],
+            [cell1, cell2, cell3, 'valign=BOTTOM', 'valign=MIDDLE', 'valign=TOP']
+        ]
+
+        # This is the table with splitByRow, which splits between row 1 & 2:
+        t = Table(tableData,
+                  colWidths=(50, 70, 70, 90, 90, 70),
+                  rowHeights=None,
+                  style=ministy,
+                  splitByRow=1,
+                  splitInRow=0)
+        parts = t.split(451, 55)
+        lst.append(parts[0])
+        lst.append(Paragraph("", styNormal))
+        lst.append(parts[1])
+
+        lst.append(Paragraph("""Here is the same table, with splitByRow=0 and
+        splitInRow=1. It splits inside a row.""",
+        styNormal))
+
+        # This is the table with splitInRow, which splits in row 2:
+        t = Table(tableData,
+                  colWidths=(50, 70, 70, 90, 90, 70),
+                  rowHeights=None,
+                  style=ministy,
+                  splitByRow=0,
+                  splitInRow=1)
+
+        parts = t.split(451, 57)
+        lst.append(parts[0])
+        lst.append(Paragraph("", styNormal))
+        lst.append(parts[1])
+
+        lst.append(Paragraph("""Here is the same table, with splitByRow=1 and
+        splitInRow=1. It splits between the rows, if possible.""",
+        styNormal))
+
+        # This is the table with both splits, which splits in row 2:
+        t = Table(tableData,
+                  colWidths=(50, 70, 70, 90, 90, 70),
+                  rowHeights=None,
+                  style=ministy,
+                  splitByRow=1,
+                  splitInRow=1)
+
+        parts = t.split(451, 57)
+        lst.append(parts[0])
+        #lst.append(Paragraph("", styNormal))
+        lst.append(parts[1])
+
+        lst.append(Paragraph("""But if we constrict the space to less than the first row,
+        it splits that row.""",
+        styNormal))
+
+        # This is the table with both splits and no space, which splits in row 1:
+        t = Table(tableData,
+                  colWidths=(50, 70, 70, 90, 90, 70),
+                  rowHeights=None,
+                  style=ministy,
+                  splitByRow=1,
+                  splitInRow=1)
+
+        parts = t.split(451, 15)
+        lst.append(parts[0])
+        lst.append(Paragraph("", styNormal))
+        lst.append(parts[1])
+
+        # Split it at a point in row 2, where the split fails
+        lst.append(Paragraph("""When splitByRow is 0 and splitInRow is 1, we should
+        still allow fallback to splitting between rows""",
+        styNormal))
+
+        t = Table(tableData,
+                  colWidths=(50, 70, 70, 90, 90, 70),
+                  rowHeights=None,
+                  style=ministy,
+                  splitByRow=0,
+                  splitInRow=1)
+        parts = t.split(451, 55)
+        lst.append(parts[0])
+        lst.append(Paragraph("", styNormal))
+        lst.append(parts[1])
+
+
         SimpleDocTemplate(outputfile('test_table_layout.pdf'), showBoundary=1).build(lst)
 
 def makeSuite():