Re: Patch to support tables with oversize cells
Lennart Regebro via reportlab-users <[email protected]> Wed, 1 Jun 2022 13:33:30 +0200
| Newsgroups | gmane.comp.python.reportlab.user |
|---|---|
| Message-ID | <CAHT-kBcK97W+ZeuMRU1ORqwQeDL7BGLcJ25eCV-yZFFN5XO2Kw@mail.gmail.com> |
Yes, two small bugs in height alignments that didn't show up in any previous test. Good find. On Wed, Jun 1, 2022 at 10:55 AM Robin Becker <[email protected]> wrote: > On 30/05/2022 15:59, Lennart Regebro wrote: > > OK, that seems to have been the only case of me setting attributes on > > T/self. > > > > However, I also added a matrixCopy() function and used it the cases > where I > > modify _cellvalues and _cellStyles, to make sure the original table > doesn't > > get modified that way. > > > ........ > > Hi Lennart, > > thanks for v8. I tested and find no problems for splitInrow=0. I see that > your examples seem OK as well. > > However, I made a slightly more extreme case with two overlapping split > rows not starting at the same row. I see that > some splits do work, but there are some glitches. > > For your convenience I put the code (extended from before) here > > https://www.reportlab.com/ftp/lennart/lennart-example.py > > > I tested by using the statement > for W in 30 40 50 60; do > python tmp/lennart-example.py --useData=3 --split=$W --splitInRow=1 > --splitByRow=0 --pdfn=tmp/ssir-$W.pdf > done > > the results are here > > https://www.reportlab.com/ftp/lennart/ssir-30.pdf #no split possible > https://www.reportlab.com/ftp/lennart/ssir-40.pdf #splits but first > column is positioned wrongly > https://www.reportlab.com/ftp/lennart/ssir-50.pdf #ditto > https://www.reportlab.com/ftp/lennart/ssir-40.pdf #splits but text is > mis-positioned in col5 > > Not exactly sure what's wrong, but it doesn't look right in these cases. > -- > Robin Becker >
splitInRow-v9.diff
(text/x-patch, 54.5 KB)
diff --git a/src/reportlab/platypus/tables.py b/src/reportlab/platypus/tables.py
index 2b1003c..707edc1 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
@@ -242,6 +243,18 @@ def spanFixDim(V0,V,spanCons,lim=None,FUZZ=rl_config._FUZZ):
for x,v in M.items():
V[x] += v
+def matrixCopy(m):
+ """Copies a list of lists
+
+ Use this to make a copy of objects that has lists of lists representing
+ rows and columns, like _cellvalues, _cellStyles etc.
+ """
+ result = []
+ for row in m:
+ result.append(row[:])
+ return result
+
+
class _ExpandedCellTuple(tuple):
pass
@@ -251,7 +264,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=0, 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 +349,7 @@ class Table(Flowable):
self.repeatRows = repeatRows
self.repeatCols = repeatCols
self.splitByRow = splitByRow
+ self.splitInRow = splitInRow
if style:
self.setStyle(style)
@@ -464,6 +478,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 +1217,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 +1374,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,20 +1485,24 @@ 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-1), weight, color, cap, dash, join, count, space))
+ A.append(('INNERGRID',(sc,n), (ec,er), 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':
- if sr<n and er>=n:
+ if sr<n and er>=n and not doInRowSplit:
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((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
@@ -1442,11 +1517,458 @@ class Table(Flowable):
else:
A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
- 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:
+ return A
+
+ def _stretchCommands(self, n, cmds, oldrowcount):
+ """Stretches the commands when a row is split
+
+ The row start is sr, the row end is er.
+
+ sr | er | result
+ ---------------------------------------------------------------------
+ <n | <n | Do nothing.
+ | >=n | A command that spans the break, extend end.
+ ---------------------------------------------------------------------
+ ==n | ==n | Zero height. Extend the end, unless it's a LINEABOVE
+ | | commands, it's between rows so do nothing.
+ | | For LINEBELOW increase both.
+ | >n | A command that spans the break, extend end.
+ ---------------------------------------------------------------------
+ >n | >n | This command comes after the break, increase both.
+ ---------------------------------------------------------------------
+
+ Summary:
+ 1. If er > n then increase er
+ 2. If sr > n then increase sr
+ 3. If er == n and sr < n, increase er
+ 4. If er == sr == n and cmd is not line, increase er
+
+ """
+ stretched = []
+ for c in cmds:
+ cmd, (sc,sr), (ec,er) = c[0:3]
+
+ if sr in ("splitlast", "splitfirst") or er in ("splitlast", "splitfirst"):
+ stretched.append(c)
+ continue
+
+ if er < 0:
+ er += oldrowcount
+ if sr < 0:
+ sr += oldrowcount
+
+ if er > n:
+ er += 1
+ elif er == n:
+ if sr < n or (sr == n and cmd != "LINEABOVE"):
+ er += 1
+
+ if sr > n or (sr == n and cmd == "LINEBELOW"):
+ sr += 1
+
+ stretched.append((c[0], (sc,sr), (ec,er)) + c[3:])
+
+ return stretched
+
+ 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,ignoreSpans=doInRowSplit)
+
+ # 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
+ data = matrixCopy(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 column, (value, style, width) in enumerate(zip(cellvalues, cellStyles, cellWidths)):
+ if self._spanCmds and self._spanRanges.get((column, n), None) is None:
+ # This is part of another cell span, the value will not be displayed
+ continue
+
+ 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) or
+ (minSplit > (availHeight - usedHeights))):
+ # We can't split this row. So we should fail, and let the split get retried
+ # with splitInRow = 0. However, if there is a spanned row that will also
+ # fail. So first we need to check if any cell in the current row is spanned.
+ if not self._spanCmds:
+ # There are no spans to look at, so we can skip this and fail directly
+ return []
+
+ splitCells = set()
+ for column in range(self._ncols):
+ cell = (column, n)
+ if (cell in self._rowSpanCells and
+ self._spanRanges.get((column, n), None) is None):
+ # This cell is a part of a rowSpan, and not the main cell.
+ # Find the real cell and cell value
+ for cell, span in self._spanRanges.items():
+ if span is None:
+ continue
+ start_col, start_row, end_col, end_row = span
+ if (column >= start_col and
+ column <= end_col and
+ n > start_row and
+ n <= end_row):
+ splitCells.add(cell)
+ break
+
+ if not splitCells:
+ # There were no spanned rows that could be split, so we fail.
+ return []
+
+ spanCmds = []
+ for cmd, (sc, sr), (ec, er) in self._spanCmds:
+ # -1 means last row/column, handle that here.
+ if sc < 0:
+ sc += self._ncols
+ if ec < 0:
+ ec += self._ncols
+ if sr < 0:
+ sr += self._nrows
+ if er < 0:
+ er += self._nrows
+ spanCmds.append((cmd, (sc, sr), (ec, er)))
+
+ newCellStyles = matrixCopy(self._cellStyles)
+ bkgrndcmds = self._bkgrndcmds
+
+ # There are cells spanning the rows we want to split. They can be split,
+ # because the _getFirstPossibleSplitRowPosition() call above checked
+ # that they are not in a nosplit range, so let's split them.
+ for cell in splitCells:
+ span_sc, span_sr, span_ec, span_er = self._spanRanges[cell]
+ spanRect = self._spanRects[cell]
+ oldHeight = spanRect[3]
+ newHeight = sum(self._rowHeights[span_sr:n])
+
+ # Copy the style:
+ oldStyle = newCellStyles[span_sr][span_sc]
+
+ res = self._splitCell(self._cellvalues[span_sr][span_sc],
+ oldStyle, oldHeight, newHeight, width)
+ if not res:
+ # Could not split
+ return []
+
+ # Replace the data values:
+ data[span_sr][span_sc] = res[0]
+ data[n][span_sc] = res[1]
+
+ # Now get replace the rowspan with two new rowspans, or
+ # remove the spans if no span remains
+
+ newSpanCmds = []
+ for cmd, start, end in spanCmds:
+ if ((span_sc, span_sr) == start and
+ (span_ec, span_er) == end):
+ # Modify this:
+ if n-1 > span_sr or span_sc != span_ec:
+ newSpanCmds.append((cmd, (span_sc, span_sr), (span_ec, n-1)))
+ if n < span_er or span_sc != span_ec:
+ newSpanCmds.append((cmd, (span_sc, n), (span_ec, span_er)))
+ else:
+ newSpanCmds.append((cmd, start, end))
+
+ spanCmds = newSpanCmds
+
+ newbkgrndcmds = []
+ for cmd, start, end, color in bkgrndcmds:
+ if start == (span_sc, span_sr):
+ # The cell we are splitting has a background color command.
+ # Add commands for the new split cells:
+ newbkgrndcmds.append((cmd, start, (end[0], n-1), color))
+ newbkgrndcmds.append((cmd, (start[0], n), (end[0], n), color))
+ else:
+ newbkgrndcmds.append((cmd, start, end, color))
+
+ bkgrndcmds = newbkgrndcmds
+
+ # And adjust the style
+ newStyle = oldStyle.copy()
+ if oldStyle.valign == "MIDDLE":
+ # Adjust margins
+ if res[0] and res[1]:
+ # We split the content, so fix up the valign:
+ oldStyle.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, oldStyle)[1]
+ margin = (curRowHeight - h) / 2
+ if v[0]:
+ oldStyle.topPadding += margin
+ elif v[1]:
+ newStyle.bottomPadding += margin
+ newCellStyles[n][span_sc] = newStyle
+
+ # Make a new table here
+ T = self.__class__( data, colWidths=self._colWidths,
+ rowHeights=self._rowHeights, 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._bkgrndcmds = bkgrndcmds
+ T._spanCmds = spanCmds
+ T._nosplitCmds = self._nosplitCmds
+ T._srflcmds = self._srflcmds
+ T._colpositions = self._colpositions
+ T._rowpositions = self._rowpositions
+
+ T._calcNoSplitRanges()
+ T._calcSpanRanges()
+ T._calcSpanRects()
+
+ # And then, remove any lines that now appear that were inside
+ # the spanning cell before the split. First, we need to split
+ # the grids, and convert the bit of the grid that spans the
+ # split to a line.
+ newlinecmds = []
+ for linecmd in self._linecmds:
+ op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space = linecmd
+ # -1 means "to the end", so we handle that here
+ if er < 0:
+ er += T._nrows
+ if ec < 0:
+ ec += T._ncols
+
+ if ((op == 'BOX') or (op == 'GRID' and (sr <= n or er >=n)) or
+ (op == 'INNERGRID' and (sr < n or er > n))):
+
+ if op in ('GRID', 'INNERGRID'):
+ newlinecmds.append(('INNERGRID',(sc,sr), (ec,n-1), weight, color, cap, dash, join, count, space))
+ newlinecmds.append(('INNERGRID',(sc,n), (ec,er), weight, color, cap, dash, join, count, space))
+ newlinecmds.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space))
+ if op in ('GRID', 'BOX'):
+ # The box must be made of lines, because otherwise
+ # it might not get split where it should, below.
+ newlinecmds.append(('LINEABOVE', (sc,sr), (ec,sr), weight, color, cap, dash, join, count, space))
+ newlinecmds.append(('LINEBELOW', (sc,er), (ec,er), weight, color, cap, dash, join, count, space))
+ newlinecmds.append(('LINEBEFORE', (sc,sr), (sc,er), weight, color, cap, dash, join, count, space))
+ newlinecmds.append(('LINEAFTER', (ec,sr), (ec,er), weight, color, cap, dash, join, count, space))
+ else:
+ newlinecmds.append(linecmd)
+ continue
+
+ # Then secondly split any LINEABOVE and LINEBELOW so that the
+ # split cells don't get them.
+ for cell in splitCells:
+
+ moddedcmds = []
+ for linecmd in newlinecmds:
+ op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space = linecmd
+ span_sc, span_sr, span_ec, span_er = self._spanRanges[cell]
+
+ if (((op == "LINEABOVE" and er > span_sr and sr <= span_er) or
+ (op == "LINEBELOW" and er >= span_sr and sr < span_er)) and
+ (sc <= span_ec and ec >= span_sc)):
+ # This needs handling of some sort
+
+ if op == "LINEABOVE":
+ startrow = span_sr
+ endrow = span_er + 1
+ else:
+ startrow = span_sr - 1
+ endrow = span_er
+
+ if sr <= startrow:
+ # Anything before the span should be unaffected:
+ moddedcmds.append(
+ (op, (sc, sr), (ec, startrow), weight, color, cap, dash, join, count, space)
+ )
+
+ # For any lines in between we need to remove the split cell
+ if span_sc > sc:
+ # The start column of the span is higher than the start column of
+ # the line. So we need a line up until but not including the start column
+ moddedcmds.append(
+ (op, (sc, max(startrow, sr)), (span_sc-1, min(er, endrow)), weight, color, cap, dash, join, count, space)
+ )
+ if span_ec < ec:
+ # The start column of the span is lower than the end column of
+ # the line. So we need a line up starting after but not including the end column
+ moddedcmds.append(
+ (op, (span_ec+1, max(startrow, sr)), (ec, min(er, endrow)), weight, color, cap, dash, join, count, space)
+ )
+
+ if er >= endrow:
+ moddedcmds.append(
+ (op, (sc, endrow), (ec, er), weight, color, cap, dash, join, count, space)
+ )
+ # Anything after the span should be unaffected:
+
+ else:
+ moddedcmds.append(linecmd)
+
+ newlinecmds = moddedcmds
+
+ T._linecmds = newlinecmds
+
+ return T._splitRows(availHeight,doInRowSplit=False)
+
+ # This is where we split the row:
+ splitPoint = min(availHeight - usedHeights, maxHeight - maxSplit)
+ if splitPoint+1 < self.splitInRow:
+ # The height of the split is smaller than the minimum.
+ # Fail, and the whole table will be moved to the next page.
+ return []
+
+ remaining = self._height - splitPoint
+ if remaining < self.splitInRow:
+ # The remaining height of the table is smaller than the minimum.
+ # Fail, and the whole table will be moved to the next page.
+ return []
+
+ 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._stretchCommands(n, self._linecmds, lim)
+ T._bkgrndcmds = self._stretchCommands(n, self._bkgrndcmds, lim)
+ T._spanCmds = self._stretchCommands(n, self._spanCmds, lim)
+ T._nosplitCmds = self._stretchCommands(n, self._nosplitCmds, lim)
+ T._srflcmds = self._stretchCommands(n, self._srflcmds, lim)
+ 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
+
+ cornerRadii = getattr(self,'_cornerRadii',None)
+ R0 = self.__class__( data[:n], colWidths=T._colWidths, rowHeights=splitH[:n],
+ repeatRows=repeatRows, repeatCols=repeatCols, splitByRow=self.splitByRow,
+ splitInRow=self.splitInRow, normalizedData=1, cellStyles=T._cellStyles[:n],
+ ident=ident,
+ spaceBefore=getattr(self,'spaceBefore',None),
+ longTableOptimize=lto,
+ cornerRadii=cornerRadii[:2] if cornerRadii else None)
+
+ nrows = T._nrows
+ ncols = T._ncols
+
+ _linecmds = T._splitLineCmds(n, doInRowSplit=doInRowSplit)
+
+ R0._cr_0(n,_linecmds,nrows)
+ 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 +1979,53 @@ 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:],
+ splitByRow=self.splitByRow, splitInRow=self.splitInRow,
+ normalizedData=1,
+ 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,_linecmds)
+ 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=self.splitByRow, splitInRow=self.splitInRow,
+ 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,_linecmds)
+ 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]
@@ -1520,9 +2045,11 @@ class Table(Flowable):
y+=1
_getRowImpossible=staticmethod(_getRowImpossible)
- def _getFirstPossibleSplitRowPosition(self,availHeight):
+ def _getFirstPossibleSplitRowPosition(self,availHeight,ignoreSpans=0):
+ # Returns the LAST possible split row position
impossible={}
- if self._spanCmds:
+ # With inRowSplits we ignore spans
+ if self._spanCmds and not ignoreSpans:
self._getRowImpossible(impossible,self._rowSpanCells,self._spanRanges)
if self._nosplitCmds:
self._getRowImpossible(impossible,self._rowNoSplitCells,self._nosplitRanges)
@@ -1540,11 +2067,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 +2091,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_inrowsplit.py b/tests/test_table_inrowsplit.py
new file mode 100644
index 0000000..006960e
--- /dev/null
+++ b/tests/test_table_inrowsplit.py
@@ -0,0 +1,419 @@
+from reportlab.lib.testutils import setOutDir,makeSuiteForClasses, outputfile, printLocation
+setOutDir(__name__)
+import operator, string
+from reportlab.platypus import *
+#from reportlab import rl_config
+from reportlab.lib.styles import PropertySet, getSampleStyleSheet, ParagraphStyle
+from reportlab.lib import colors
+from reportlab.lib.units import inch
+from reportlab.platypus.paragraph import Paragraph
+#from reportlab.lib.utils import fp_str
+#from reportlab.pdfbase import pdfmetrics
+from reportlab.platypus.flowables import PageBreak
+import os
+import unittest
+
+class TableTestCase(unittest.TestCase):
+
+
+ def getDataBlock(self):
+ "Helper - data for our spanned table"
+ return [
+ # two rows are for headers
+ ['Region','Product','Period',None,None,None,'Total'],
+ [None,None,'Q1','Q2','Q3','Q4',None],
+
+ # now for data
+ ['North','Spam',100,110,120,130,460],
+ ['North','Eggs',101,111,121,131,464],
+ ['North','Guinness',102,112,122,132,468],
+
+ ['South','Spam',100,110,120,130,460],
+ ['South','Eggs',101,111,121,131,464],
+ ['South','Guinness',102,112,122,132,468],
+ ]
+
+ def test_document(self):
+
+ rowheights = (24, 16, 16, 16, 16)
+ rowheights2 = (24, 16, 16, 16, 30)
+ colwidths = (50, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32)
+ GRID_STYLE = TableStyle(
+ [('GRID', (0,0), (-1,-1), 0.25, colors.black),
+ ('ALIGN', (1,1), (-1,-1), 'RIGHT')]
+ )
+
+ styleSheet = getSampleStyleSheet()
+ styNormal = styleSheet['Normal']
+ styNormal.spaceBefore = 6
+ styNormal.spaceAfter = 6
+
+ lst = []
+ lst.append(Paragraph("""Oversized cells""", styleSheet['Heading1']))
+
+ lst.append(Paragraph("""Cells can end up being larger than a page.
+ In that case, we need to split the cell. splitByRow and splitInRow
+ controls that. By default splitByRow is 1 and splitInRow is 0.
+ It splits between 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.""",
+ styNormal)]
+ cell2 = [Paragraph("A cell with 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']
+ ]
+ colWidths = (50, 75, 70, 90, 90, 70)
+
+ # This is the table with splitByRow, which splits between row 1 & 2:
+ t = Table(tableData,
+ colWidths=colWidths,
+ rowHeights=None,
+ style=ministy,
+ splitByRow=1,
+ splitInRow=0)
+ parts = t.split(451, 60)
+ lst.append(parts[0])
+ lst.append(Spacer(0,6))
+ 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=colWidths,
+ rowHeights=None,
+ style=ministy,
+ splitByRow=0,
+ splitInRow=1)
+
+ parts = t.split(451, 60)
+ lst.append(parts[0])
+ lst.append(Spacer(0,6))
+ 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=colWidths,
+ rowHeights=None,
+ style=ministy,
+ splitByRow=1,
+ splitInRow=1)
+
+ parts = t.split(451, 60)
+ lst.append(parts[0])
+ lst.append(Spacer(0,6))
+ 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=colWidths,
+ rowHeights=None,
+ style=ministy,
+ splitByRow=1,
+ splitInRow=1)
+
+ parts = t.split(451, 15)
+ lst.append(parts[0])
+ lst.append(Spacer(0,6))
+ 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=colWidths,
+ rowHeights=None,
+ style=ministy,
+ splitByRow=0,
+ splitInRow=1)
+ parts = t.split(451, 50)
+ lst.append(parts[0])
+ lst.append(Spacer(0,6))
+ lst.append(parts[1])
+
+ lst.append(PageBreak())
+ lst.append(Paragraph("""Long cell with multiple splits, and minimum split size""",
+ styleSheet['Heading2']))
+
+ lst.append(Paragraph("With a height of 80 amd splitInRow=1 (no minimum rest) "
+ "we get a small last split.",
+ styleSheet['Normal']))
+
+ ministy = TableStyle([('GRID', (0,0), (-1,-1), 1.0, colors.black),])
+ cell1 = [Paragraph(
+ "This is a very very tall cell to make a very very tall row so we can split it "
+ "many many times, and also test for minimum splits.""",
+ styNormal)]
+
+ tableData = [['Row 1', 'Row 1'],[cell1, 'Cell2']]
+ colWidths = (50,50)
+
+ # This is the table with splitByRow, which splits between row 1 & 2:
+ t = Table(tableData,
+ colWidths=colWidths,
+ rowHeights=None,
+ style=ministy,
+ splitByRow=0,
+ splitInRow=1)
+
+ while True:
+ s = t.split(4*inch, 80)
+ lst.append(s[0])
+ lst.append(Spacer(6,6))
+ if len(s) > 1:
+ t = s[1]
+ else:
+ break
+
+ # Now the same, but with a minimum size of 40.
+ lst.append(Paragraph("With minimum split size to 40 (splitInRow=40) the last split "
+ "isn't done, and it should instead flow over to the next page.",
+ styleSheet['Normal']))
+ t = Table(tableData,
+ colWidths=colWidths,
+ rowHeights=None,
+ style=ministy,
+ splitByRow=0,
+ splitInRow=40)
+
+ while True:
+ s = t.split(4*inch, 80)
+ if s:
+ lst.append(s[0])
+ lst.append(Spacer(6,6))
+ else:
+ lst.append(t)
+ if len(s) > 1:
+ t = s[1]
+ else:
+ break
+
+ # It should also not split at less than 40 in the beginning.
+ assert not t.split(4*inch, 30)
+
+ lst.append(PageBreak())
+ lst.append(Paragraph("""Style handling splitInRow""", styleSheet['Heading2']))
+
+ tableData = [
+ ['00\n\naa', '01', '02', '03', '04'],
+ ['10', '11\nbb', '12', '13', '14'],
+ ['20', '21', '22\ncc', '23', '24'],
+ ['30', '31', '32', '33\ndd', '34']
+ ]
+ styles = [
+ ('GRID',(0,0),(-1,-1),0.5,colors.grey),
+ ('GRID',(1,1),(-2,-2),1,colors.green),
+ ('BOX',(0,0),(1,-1),2,colors.red),
+ ('BOX',(0,0),(-1,-1),2,colors.black),
+ ('LINEABOVE',(1,2),(-2,2),1,colors.blue),
+ ('LINEBEFORE',(2,1),(2,-2),1,colors.pink),
+ ('BACKGROUND', (0, 0), (0, 1), colors.pink),
+ ('BACKGROUND', (1, 1), (1, 2), colors.lavender),
+ ('BACKGROUND', (2, 2), (2, 3), colors.orange),
+ ('TEXTCOLOR',(0,-1),(-2,-1),colors.green),
+ ]
+
+ t = Table(tableData,style=styles, splitInRow=1, splitByRow=0)
+ for split in (36, 60, 100, 130,):
+ parts = t.split(4*inch, split)
+ lst.append(parts[0])
+ lst.append(Spacer(0,6))
+ lst.append(parts[1])
+ lst.append(Spacer(0,12))
+
+ lst.append(PageBreak())
+ lst.append(Paragraph("""Splitfirst/splitlast behavior with split rows and spans""",
+ styleSheet['Heading2']))
+
+ data= [['A', 'BBBBB', 'C', 'D', 'E'],
+ ['00', '01', '02', '03', '04'],
+ ['10\n11', ],
+ ['20', '21', '22', '23', '24'],
+ ['30', '31', '32', '33', '34']]
+ sty = [
+ ('ALIGN',(0,0),(-1,-1),'CENTER'),
+ ('VALIGN',(0,0),(-1,-1),'TOP'),
+ ('GRID',(0,0),(-1,-1),1,colors.green),
+ ('BOX',(0,0),(-1,-1),2,colors.red),
+
+ #span 'BBBB' across middle 3 cells in top row
+ ('SPAN',(1,0),(3,0)),
+ #now color the first cell in this range only,
+ #i.e. the one we want to have spanned. Hopefuly
+ #the range of 3 will come out khaki.
+ ('BACKGROUND',(1,0),(1,0),colors.khaki),
+
+ ('SPAN',(0,2),(-1,2)),
+
+ #span 'AAA'down entire left column
+ ('SPAN',(0,0), (0, 1)),
+ ('BACKGROUND',(0,0),(0,0),colors.cyan),
+ ('TEXTCOLOR', (0,'splitfirst'), (-1,'splitfirst'), colors.cyan),
+ ('TEXTCOLOR', (0,'splitlast'), (-1,'splitlast'), colors.red),
+ ('BACKGROUND', (0,'splitlast'), (-1,'splitlast'), colors.pink),
+ ('LINEBELOW', (0,'splitlast'), (-1,'splitlast'), 1, colors.grey,'butt'),
+ ]
+ t=Table(data,style=sty, colWidths = [20] * 5, splitInRow=1, splitByRow=0)
+ lst.append(t)
+ lst.append(Spacer(18,18))
+
+ for split in (40, 60, 75):
+ for s in t.split(4*inch, split):
+ lst.append(s)
+ lst.append(Spacer(0,6))
+ lst.append(Spacer(18,12))
+
+ lst.append(PageBreak())
+ lst.append(Paragraph("""Style handling with rowspans""", styleSheet['Heading2']))
+
+ data = [
+ ['R0C0\nR1C0\nR2C0', 'R0C1', 'R0C2', 'R0C3', 'R0C4\nR1C4'],
+ ['', 'R1C1', 'R1C2', 'R1C3', ''],
+ ['', 'R2C1', 'R2C2', 'R2C3', 'R2C4'],
+ ['R3C0', 'R3C1', 'R3C2', 'R3C3 R3C4\nR4C3 R4C4', ''],
+ ['R4C0', 'R4C1', 'R4C2', '', ''],
+ ]
+
+ sty = [
+ ('GRID',(0,0),(-1,-1),0.5,colors.grey),
+ ('GRID',(1,1),(-2,-2),1,colors.green),
+ ('BOX',(0,0),(-1,-1),3,colors.black),
+ ('BOX',(0,0),(1,-1),2,colors.red),
+ ('LINEABOVE',(1,2),(-2,2),1,colors.blue),
+ ('LINEBEFORE',(2,1),(2,-2),1,colors.pink),
+ ('BACKGROUND', (0, 0), (0, 0), colors.pink),
+ ('BACKGROUND', (1, 1), (1, 2), colors.lavender),
+ ('BACKGROUND', (2, 3), (2, 4), colors.orange),
+ ('TEXTCOLOR',(0,0),(-1,0),colors.brown),
+ ('TEXTCOLOR',(1,1),(-2,-1),colors.green),
+ ('SPAN',(0,0), (0, 2)),
+ ('SPAN',(2,0), (2, 1)),
+ ('SPAN',(3,3), (4, 4)),
+ ('SPAN',(4,0), (4, 1)),
+ ]
+ t=Table(data,style=sty, colWidths = [40] * 5, splitInRow=1, splitByRow=0)
+ lst.append(t)
+ lst.append(Spacer(18,18))
+
+ for split in (30, 45, 60, 75):
+ for s in t.split(4*inch, split):
+ lst.append(s)
+ lst.append(Spacer(0,6))
+ lst.append(Spacer(18,18))
+
+ lst.append(PageBreak())
+ lst.append(Paragraph("""More Style handling with rowspans""", styleSheet['Heading2']))
+
+ data = [
+ ['R0C0', 'R0C1', 'R0C2', 'R0C3', 'R0C4'],
+ ['R1C0', 'R1C1', 'R1C2', 'R1C3', 'R1C4'],
+ ['R2C0', 'R2C1', 'R2C2', 'R2C3', 'R2C4'],
+ ['R3C0', 'R3C1', 'R3C2', 'R3C3', 'R3C4'],
+ ['R4C0', 'R4C1', 'R4C2', 'R4C3', 'R4C4'],
+ ]
+
+ sty = [
+ ('GRID',(0,0),(-1,-1),0.5,colors.grey),
+ ('GRID',(1,1),(1,-1),1,colors.green),
+ ('BOX',(0,0),(-3,-3),3,colors.black),
+ ('BOX',(1,1),(-1,-1),2,colors.red),
+ ('LINEABOVE',(1,1),(2,2),1,colors.blue),
+ ('LINEBELOW',(2,2),(4,4),1,colors.pink),
+ ('BACKGROUND', (0, 0), (0, 0), colors.pink),
+ ('BACKGROUND', (1, 1), (1, 2), colors.lavender),
+ ('BACKGROUND', (4, 3), (4, 4), colors.orange),
+ ('TEXTCOLOR',(0,0),(-1,0),colors.brown),
+ ('TEXTCOLOR',(1,1),(-2,-1),colors.green),
+ ('SPAN',(0,0), (0, -1)),
+ ('SPAN',(2,1), (-2, 3)),
+ ]
+ t=Table(data,style=sty, colWidths = [40] * 5, splitInRow=1, splitByRow=0)
+ lst.append(t)
+ lst.append(Spacer(18,18))
+
+ for split in (30, 45, 60, 75):
+ for s in t.split(4*inch, split):
+ lst.append(s)
+ lst.append(Spacer(0,6))
+ lst.append(Spacer(18,18))
+
+ lst.append(PageBreak())
+ lst.append(Paragraph("""Testing for a couple of height alignment bugs""", styleSheet['Heading2']))
+
+ data = [
+ ['R0C0\nR1C0\nR2C0', 'R0C1', 'R0C2', 'R0C3', 'R0C4\nR1C4', 'R0C5'],
+ ['', 'R1C1', 'R1C2', 'R1C3', '', 'R1C5\nR2C5\nR3C5'],
+ ['', 'R2C1', 'R2C2', 'R2C3', 'R2C4', ''],
+ ['R3C0', 'R3C1', 'R3C2', 'R3C3 R3C4\nR4C3 R4C4', '', ''],
+ ['R4C0', 'R4C1', 'R4C2', '', '', 'R4C5'],
+ ]
+
+ sty = [
+ ('GRID',(0,0),(-1,-1),0.5,colors.grey),
+ ('GRID',(1,1),(-2,-2),1,colors.green),
+ ('BOX',(0,0),(-1,-1),3,colors.black),
+ ('BOX',(0,0),(1,-1),2,colors.red),
+ ('LINEABOVE',(1,2),(-2,2),1,colors.blue),
+ ('LINEBEFORE',(2,1),(2,-2),1,colors.pink),
+ ('BACKGROUND', (0, 0), (0, 0), colors.pink),
+ ('BACKGROUND', (1, 1), (1, 2), colors.lavender),
+ ('BACKGROUND', (2, 3), (2, 4), colors.orange),
+ ('BACKGROUND',(5,1),(5,3),colors.greenyellow),
+ ('BACKGROUND',(5,4),(5,4),colors.darkviolet),
+ ('TEXTCOLOR',(0,0),(-1,0),colors.brown),
+ ('TEXTCOLOR',(1,1),(-2,-1),colors.green),
+ ('TEXTCOLOR',(5,1),(5,3),colors.magenta),
+ ('TEXTCOLOR',(5,4),(5,4),colors.white),
+ ('SPAN',(0,0), (0, 2)),
+ ('SPAN',(2,0), (2, 1)),
+ ('SPAN',(3,3), (4, 4)),
+ ('SPAN',(4,0), (4, 1)),
+ ('SPAN',(5,1), (5, 3)),
+ ]
+ t=Table(data,style=sty, colWidths = [40] * 6, splitInRow=1, splitByRow=0)
+ lst.append(t)
+
+ for split in (30, 40, 50, 60):
+ for s in t.split(4*inch, split):
+ lst.append(s)
+ lst.append(Spacer(0,6))
+ lst.append(Spacer(18,18))
+
+ SimpleDocTemplate(outputfile('test_table_inrowsplit.pdf'), showBoundary=1).build(lst)
+
+def makeSuite():
+ return makeSuiteForClasses(TableTestCase)
+
+
+#noruntests
+if __name__ == "__main__":
+ unittest.TextTestRunner().run(makeSuite())
+ print('saved '+outputfile('test_table_inrowsplit.pdf'))
+ printLocation()
test_table_inrowsplit.pdf
(application/pdf, 13.7 KB) - not displayed