--- a/reportlab/platypus/tables.py Thu Apr 17 23:37:39 2003 +0000
+++ b/reportlab/platypus/tables.py Mon Apr 21 22:27:49 2003 +0000
@@ -1,8 +1,8 @@
#copyright ReportLab Inc. 2000
#see license.txt for license details
#history http://cvs.sourceforge.net/cgi-bin/cvsweb.cgi/reportlab/platypus/tables.py?cvsroot=reportlab
-#$Header: /tmp/reportlab/reportlab/platypus/tables.py,v 1.63 2003/04/05 23:46:42 andy_robinson Exp $
-__version__=''' $Id: tables.py,v 1.63 2003/04/05 23:46:42 andy_robinson Exp $ '''
+#$Header: /tmp/reportlab/reportlab/platypus/tables.py,v 1.64 2003/04/21 22:27:47 andy_robinson Exp $
+__version__=''' $Id: tables.py,v 1.64 2003/04/21 22:27:47 andy_robinson Exp $ '''
__doc__="""
Tables are created by passing the constructor a tuple of column widths, a tuple of row heights and the data in
row order. Drawing of the table can be controlled by using a TableStyle instance. This allows control of the
@@ -234,32 +234,50 @@
return w, t - V[0].getSpaceBefore()-V[-1].getSpaceAfter()
def _calc_width(self):
-
- W = self._argW
-
+ #comments added by Andy to Robin's slightly
+ #terse variable names
+ W = self._argW #widths array
+ #print 'widths array = %s' % str(self._colWidths)
canv = getattr(self,'canv',None)
saved = None
- if None in W:
+ if None in W: #some column widths are not given
+ if self._spanCmds:
+ colspans = self._colSpannedCells
+ else:
+ colspans = {}
+## k = colspans.keys()
+## k.sort()
+## print 'the following cells are part of spanned ranges: %s' % k
W = W[:]
self._colWidths = W
while None in W:
- j = W.index(None)
+ j = W.index(None) #find first unspecified column
+ #print 'sizing column %d' % j
f = lambda x,j=j: operator.getitem(x,j)
- V = map(f,self._cellvalues)
- S = map(f,self._cellStyles)
+ V = map(f,self._cellvalues) #values for this column
+ S = map(f,self._cellStyles) #styles for this column
w = 0
i = 0
+
for v, s in map(None, V, S):
+ #if the current cell is part of a spanned region,
+ #assume a zero size.
+ if colspans.has_key((j, i)):
+ #print 'sizing a spanned cell (%d, %d) with content "%s"' % (j, i, str(v))
+ t = 0.0
+ else:#work out size
+ t = type(v)
+ if t in _SeqTypes or isinstance(v,Flowable):
+ raise ValueError, "Flowable %s in cell(%d,%d) can't have auto width\n%s" % (v.identity(30),i,j,self.identity(30))
+ elif t is not StringType: v = v is None and '' or str(v)
+ v = string.split(v, "\n")
+ t = s.leftPadding+s.rightPadding + max(map(lambda a, b=s.fontname,
+ c=s.fontsize,d=pdfmetrics.stringWidth: d(a,b,c), v))
+ if t>w: w = t #record a new maximum
i = i + 1
- t = type(v)
- if t in _SeqTypes or isinstance(v,Flowable):
- raise ValueError, "Flowable %s in cell(%d,%d) can't have auto width\n%s" % (v.identity(30),i,j,self.identity(30))
- elif t is not StringType: v = v is None and '' or str(v)
- v = string.split(v, "\n")
- t = s.leftPadding+s.rightPadding + max(map(lambda a, b=s.fontname,
- c=s.fontsize,d=pdfmetrics.stringWidth: d(a,b,c), v))
- if t>w: w = t #record a new maximum
+
+ #print 'max width for column %d is %0.2f' % (j, w)
W[j] = w
width = 0
@@ -280,6 +298,13 @@
canv = getattr(self,'canv',None)
saved = None
+ #get a handy list of any cells which span rows.
+ #these should be ignored for sizing
+ if self._spanCmds:
+ spans = self._rowSpannedCells
+ else:
+ spans = {}
+
if None in H:
if canv: saved = canv._fontname, canv._fontsize, canv._leading
H = H[:] #make a copy as we'll change it
@@ -291,26 +316,30 @@
h = 0
j = 0
for v, s, w in map(None, V, S, W): # value, style, width (lengths must match)
+ if spans.has_key((j, i)):
+ t = 0.0 # don't count it, it's either occluded or unreliable
+ else:
+ t = type(v)
+ if t in _SeqTypes or isinstance(v,Flowable):
+ if not t in _SeqTypes: v = (v,)
+ if w is None:
+ raise ValueError, "Flowable %s in cell(%d,%d) can't have auto width in\n%s" % (v[0].identity(30),i,j,self.identity(30))
+ if canv: canv._fontname, canv._fontsize, canv._leading = s.fontname, s.fontsize, s.leading or 1.2*s.fontsize
+ dW,t = self._listCellGeom(v,w,s)
+ if canv: canv._fontname, canv._fontsize, canv._leading = saved
+ #print "leftpadding, rightpadding", s.leftPadding, s.rightPadding
+ dW = dW + s.leftPadding + s.rightPadding
+ if not rl_config.allowTableBoundsErrors and dW>w:
+ raise "LayoutError", "Flowable %s (%sx%s points) too wide for cell(%d,%d) (%sx* points) in\n%s" % (v[0].identity(30),fp_str(dW),fp_str(t),i,j, fp_str(w), self.identity(30))
+ else:
+ if t is not StringType:
+ v = v is None and '' or str(v)
+ v = string.split(v, "\n")
+ t = s.leading*len(v)
+ t = t+s.bottomPadding+s.topPadding
+ if t>h: h = t #record a new maximum
j = j + 1
- t = type(v)
- if t in _SeqTypes or isinstance(v,Flowable):
- if not t in _SeqTypes: v = (v,)
- if w is None:
- raise ValueError, "Flowable %s in cell(%d,%d) can't have auto width in\n%s" % (v[0].identity(30),i,j,self.identity(30))
- if canv: canv._fontname, canv._fontsize, canv._leading = s.fontname, s.fontsize, s.leading or 1.2*s.fontsize
- dW,t = self._listCellGeom(v,w,s)
- if canv: canv._fontname, canv._fontsize, canv._leading = saved
- #print "leftpadding, rightpadding", s.leftPadding, s.rightPadding
- dW = dW + s.leftPadding + s.rightPadding
- if not rl_config.allowTableBoundsErrors and dW>w:
- raise "LayoutError", "Flowable %s (%sx%s points) too wide for cell(%d,%d) (%sx* points) in\n%s" % (v[0].identity(30),fp_str(dW),fp_str(t),i,j, fp_str(w), self.identity(30))
- else:
- if t is not StringType:
- v = v is None and '' or str(v)
- v = string.split(v, "\n")
- t = s.leading*len(v)
- t = t+s.bottomPadding+s.topPadding
- if t>h: h = t #record a new maximum
+
H[i] = h
height = self._height = reduce(operator.add, H, 0)
@@ -321,10 +350,25 @@
self._rowpositions.append(height)
assert abs(height)<1e-8, 'Internal height error'
- def _calc(self):
+ def _calc(self, availWidth, availHeight):
if hasattr(self,'_width'): return
+ #in some cases there are unsizable things in
+ #cells. If so, apply a different algorithm
+ #and assign some withs in a dumb way.
+ #this CHANGES the widths array.
+ if None in self._colWidths:
+ if self._hasUnsizableElements():
+ self._calcPreliminaryWidths(availWidth)
+
+ # need to know which cells are part of spanned
+ # ranges, so _calc_height and _calc_width can ignore them
+ # in sizing
+ if self._spanCmds:
+ self._calcSpanRanges()
+
# calculate the full table height
+ #print 'during calc, self._colWidths=', self._colWidths
self._calc_height()
# if the width has already been calculated, don't calculate again
@@ -335,24 +379,110 @@
# calculate the full table width
self._calc_width()
+
if self._spanCmds:
+ #now work out the actual rect for each spanned cell
+ #from the underlying grid
self._calcSpanRects()
-
+
+ def _hasUnsizableElements(self, upToRow=None):
+ """Check for flowables in table cells and warn up front.
+
+ Allow a couple which we know are fixed size such as
+ images and graphics."""
+ bad = 0
+ if upToRow is None: upToRow = self._nrows
+ for row in range(min(self._nrows, upToRow)):
+ for col in range(self._ncols):
+ value = self._cellvalues[row][col]
+ if not self._canSize(value):
+ bad = 1
+ #raise Exception('Unsizable elements found at row %d column %d in table with content:\n %s' % (row, col, value))
+ return bad
+
+ def _canSize(self, thing):
+ "Can we work out the width quickly?"
+ if type(thing) in (ListType, TupleType):
+ for elem in thing:
+ if not self._canSize(elem):
+ return 0
+ return 1
+ elif isinstance(thing, Flowable):
+ return 0 # must loosen this up
+ else: #string, number, None etc.
+ #anything else gets passed to str(...)
+ # so should be sizable
+ return 1
+
+ def _calcPreliminaryWidths(self, availWidth):
+ """Fallback algorithm for when main one fails.
- def _calcSpanRects(self):
+ Where exact width info not given but things like
+ paragraphs might be present, do a preliminary scan
+ and assign some sensible values - just divide up
+ all unsizeable columns by the remaining space."""
+ verbose = 0
+ totalDefined = 0.0
+ numberUndefined = 0
+ for w in self._colWidths:
+ if w is None:
+ numberUndefined = numberUndefined + 1
+ else:
+ totalDefined = totalDefined + w
+ if verbose: print 'prelim width calculation. %d columns, %d undefined width, %0.2f units remain' % (
+ self._ncols, numberUndefined, availWidth - totalDefined)
+
+ #check columnwise in each None column to see if they are sizable.
+ given = []
+ sizeable = []
+ unsizeable = []
+ for colNo in range(self._ncols):
+ if self._colWidths[colNo] is None:
+ siz = 1
+ for rowNo in range(self._nrows):
+ value = self._cellvalues[rowNo][colNo]
+ if not self._canSize(value):
+ siz = 0
+ break
+ if siz:
+ sizeable.append(colNo)
+ else:
+ unsizeable.append(colNo)
+ else:
+ given.append(colNo)
+ if len(given) == self._ncols:
+ return
+ if verbose: print 'predefined width: ',given
+ if verbose: print 'uncomputable width: ',unsizeable
+ if verbose: print 'computable width: ',sizeable
+
+ #how much width is left:
+ # on the next iteration we could size the sizeable ones, for now I'll just
+ # divide up the space
+ newColWidths = list(self._colWidths)
+ guessColWidth = (availWidth - totalDefined) / (len(unsizeable)+len(sizeable))
+ assert guessColWidth >= 0, "table is too wide already, cannot choose a sane width for undefined columns"
+ if verbose: print 'assigning width %0.2f to all undefined columns' % guessColWidth
+ for colNo in sizeable:
+ newColWidths[colNo] = guessColWidth
+ for colNo in unsizeable:
+ newColWidths[colNo] = guessColWidth
+
+ self._colWidths = newColWidths
+ self._argW = newColWidths
+ if verbose: print 'new widths are:', self._colWidths
+
+
+ def _calcSpanRanges(self):
"""Work out rects for tables which do row and column spanning.
- This is a first try. The idea is to do the ordinary sizing
- first and then make two mappings:
-
+ This creates some mappings to let the later code determine
+ if a cell is part of a "spanned" range.
self._spanRanges shows the 'coords' in integers of each
'cell range', or None if it was clobbered:
(col, row) -> (col0, row0, col1, row1)
- self._spanRects shows the real coords for drawing:
- (col, row) -> (x, y, width, height)
-
- for each cell. Any cell which 'does not exist' as another
- has spanned over it will get a None entry on the right
+
+ Any cell not in the key is not part of a spanned region
"""
spanRanges = {}
for row in range(self._nrows):
@@ -382,16 +512,51 @@
# set the main entry
spanRanges[x0,y0] = (x0, y0, x1, y1)
+## from pprint import pprint as pp
+## pp(spanRanges)
self._spanRanges = spanRanges
+
+ #now produce a "listing" of all cells which
+ #are part of a spanned region, so the normal
+ #sizing algorithm can not bother sizing such cells
+ colSpannedCells = {}
+ for (key, value) in spanRanges.items():
+ if value is None:
+ colSpannedCells[key] = 1
+ elif len(value) == 4:
+ if value[0] == value[2]:
+ #not colspanned
+ pass
+ else:
+ colSpannedCells[key] = 1
+ self._colSpannedCells = colSpannedCells
+ #ditto for row-spanned ones.
+ rowSpannedCells = {}
+ for (key, value) in spanRanges.items():
+ if value is None:
+ rowSpannedCells[key] = 1
+ elif len(value) == 4:
+ if value[1] == value[3]:
+ #not rowspanned
+ pass
+ else:
+ rowSpannedCells[key] = 1
+ self._rowSpannedCells = rowSpannedCells
- # now make map 2. This maps (col, row) to the actual
- #rectangle to draw with x,y,width,height info
-## print 'rowpositions = ', self._rowpositions
-## print 'rowHeights = ', self._rowHeights
-## print 'colpositions = ', self._colpositions
-## print 'colWidths = ', self._colWidths
+
+ def _calcSpanRects(self):
+ """Work out rects for tables which do row and column spanning.
+
+ Based on self._spanRanges, which is already known,
+ and the widths which were given or previously calculated,
+ self._spanRects shows the real coords for drawing:
+ (col, row) -> (x, y, width, height)
+
+ for each cell. Any cell which 'does not exist' as another
+ has spanned over it will get a None entry on the right
+ """
spanRects = {}
- for (coord, value) in spanRanges.items():
+ for (coord, value) in self._spanRanges.items():
if value is None:
spanRects[coord] = None
else:
@@ -405,11 +570,6 @@
self._spanRects = spanRects
-## from pprint import pprint as pp
-## print 'span ranges:'
-## pp(spanRanges)
-## print '\ncell rects:'
-## pp(spanRects)
def setStyle(self, tblstyle):
if type(tblstyle) is not TableStyleType:
@@ -529,7 +689,7 @@
self._drawVLines((sc+1, sr), (ec+1, er), weight, color)
def wrap(self, availWidth, availHeight):
- self._calc()
+ self._calc(availWidth, availHeight)
#nice and easy, since they are predetermined size
self.availWidth = availWidth
return (self._width, self._height)
@@ -665,7 +825,7 @@
return [R0,R1]
def split(self, availWidth, availHeight):
- self._calc()
+ self._calc(availWidth, availHeight)
if self.splitByRow:
if self._width>availWidth: return []
return self._splitRows(availHeight)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/reportlab/test/test_table_layout.py Mon Apr 21 22:27:49 2003 +0000
@@ -0,0 +1,353 @@
+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.platypus.paragraph import Paragraph
+#from reportlab.lib.utils import fp_str
+#from reportlab.pdfbase import pdfmetrics
+from reportlab.platypus.flowables import PageBreak
+
+
+import os
+
+from reportlab.test import unittest
+from reportlab.test.utils import makeSuiteForClasses
+
+
+from types import TupleType, ListType, StringType
+
+
+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
+
+ data = (
+ ('', 'Jan', 'Feb', 'Mar','Apr','May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'),
+ ('Mugs', 0, 4, 17, 3, 21, 47, 12, 33, 2, -2, 44, 89),
+ ('T-Shirts', 0, 42, 9, -3, 16, 4, 72, 89, 3, 19, 32, 119),
+ ('Miscellaneous accessories', 0,0,0,0,0,0,1,0,0,0,2,13),
+ ('Hats', 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, 453, '1,344','2,843')
+ )
+ data2 = (
+ ('', 'Jan', 'Feb', 'Mar','Apr','May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'),
+ ('Mugs', 0, 4, 17, 3, 21, 47, 12, 33, 2, -2, 44, 89),
+ ('T-Shirts', 0, 42, 9, -3, 16, 4, 72, 89, 3, 19, 32, 119),
+ ('Key Ring', 0,0,0,0,0,0,1,0,0,0,2,13),
+ ('Hats\nLarge', 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, 453, '1,344','2,843')
+ )
+
+
+ data3 = (
+ ('', 'Jan', 'Feb', 'Mar','Apr','May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'),
+ ('Mugs', 0, 4, 17, 3, 21, 47, 12, 33, 2, -2, 44, 89),
+ ('T-Shirts', 0, 42, 9, -3, 16, 4, 72, 89, 3, 19, 32, 119),
+ ('Key Ring', 0,0,0,0,0,0,1,0,0,0,2,13),
+ (Paragraph("Let's <b>really mess things up with a <i>paragraph</i>",styNormal),
+ 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, 453, '1,344','2,843')
+ )
+
+ lst = []
+
+
+ lst.append(Paragraph("""Basics about column sizing and cell contents""", styleSheet['Heading1']))
+
+ t1 = Table(data, colwidths, rowheights)
+ t1.setStyle(GRID_STYLE)
+ lst.append(Paragraph("This is GRID_STYLE with explicit column widths. Each cell contains a string or number\n", styleSheet['BodyText']))
+ lst.append(t1)
+ lst.append(Spacer(18,18))
+
+ t2 = Table(data, None, None)
+ t2.setStyle(GRID_STYLE)
+ lst.append(Paragraph("""This is GRID_STYLE with no size info. It
+ does the sizes itself, measuring each text string
+ and computing the space it needs. If the text is
+ too wide for the frame, the table will overflow
+ as seen here.""",
+ styNormal))
+ lst.append(t2)
+ lst.append(Spacer(18,18))
+
+ t3 = Table(data2, None, None)
+ t3.setStyle(GRID_STYLE)
+ lst.append(Paragraph("""This demonstrates the effect of adding text strings with
+ newlines to a cell. It breaks where you specify, and if rowHeights is None (i.e
+ automatic) then you'll see the effect. See bottom left cell.""",
+ styNormal))
+ lst.append(t3)
+ lst.append(Spacer(18,18))
+
+
+ colWidths = (None, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32)
+ t3 = Table(data3, colWidths, None)
+ t3.setStyle(GRID_STYLE)
+ lst.append(Paragraph("""This table does not specify the size of the first column,
+ so should work out a sane one. In this case the element
+ at bottom left is a paragraph, which has no intrinsic size
+ (the height and width are a function of each other). So,
+ it tots up the extra space in the frame and divides it
+ between any such unsizeable columns. As a result the
+ table fills the width of the frame (except for the
+ 6 point padding on either size).""",
+ styNormal))
+ lst.append(t3)
+ lst.append(PageBreak())
+
+ lst.append(Paragraph("""Row and Column spanning""", styleSheet['Heading1']))
+
+ lst.append(Paragraph("""This shows a very basic table. We do a faint pink grid
+ to show what's behind it - imagine this is not printed, as we'll overlay it later
+ with some black lines. We're going to "span" some cells, and have put a
+ value of None in the data to signify the cells we don't care about.
+ (In real life if you want an empty cell, put '' in it rather than None). """, styNormal))
+
+ sty = TableStyle([
+ #very faint grid to show what's where
+ ('GRID', (0,0), (-1,-1), 0.25, colors.pink),
+ ])
+
+ t = Table(self.getDataBlock(), colWidths=None, rowHeights=None, style=sty)
+ lst.append(t)
+
+
+
+ lst.append(Paragraph("""We now center the text for the "period"
+ across the four cells for each quarter. To do this we add a 'span'
+ command to the style to make the cell at row 1 column 3 cover 4 cells,
+ and a 'center' command for all cells in the top row. The spanning
+ is not immediately evident but trust us, it's happening - the word
+ 'Period' is centered across the 4 columns. Note also that the
+ underlying grid shows through. All line drawing commands apply
+ to the underlying grid, so you have to take care what you put
+ grids through.""", styNormal))
+ sty = TableStyle([
+ #
+ ('GRID', (0,0), (-1,-1), 0.25, colors.pink),
+ ('ALIGN', (0,0), (-1,0), 'CENTER'),
+ ('SPAN', (2,0), (5,0)),
+ ])
+
+ t = Table(self.getDataBlock(), colWidths=None, rowHeights=None, style=sty)
+ lst.append(t)
+
+ lst.append(Paragraph("""We repeat this for the words 'Region', Product'
+ and 'Total', which each span the top 2 rows; and for 'Nprth' and 'South'
+ which span 3 rows. At the moment each cell's alignment is the default
+ (bottom), so these words appear to have "dropped down"; in fact they
+ are sitting on the bottom of their allocated ranges. You will just see that
+ all the 'None' values vanished, as those cells are not drawn any more.""", styNormal))
+ sty = TableStyle([
+ #
+ ('GRID', (0,0), (-1,-1), 0.25, colors.pink),
+ ('ALIGN', (0,0), (-1,0), 'CENTER'),
+ ('SPAN', (2,0), (5,0)),
+ #span the other column heads down 2 rows
+ ('SPAN', (0,0), (0,1)),
+ ('SPAN', (1,0), (1,1)),
+ ('SPAN', (6,0), (6,1)),
+ #span the 'north' and 'south' down 3 rows each
+ ('SPAN', (0,2), (0,4)),
+ ('SPAN', (0,5), (0,7)),
+ ])
+
+ t = Table(self.getDataBlock(), colWidths=None, rowHeights=None, style=sty)
+ lst.append(t)
+
+
+ lst.append(PageBreak())
+
+
+ lst.append(Paragraph("""Now we'll tart things up a bit. First,
+ we set the vertical alignment of each spanned cell to 'middle'.
+ Next we add in some line drawing commands which do not slash across
+ the spanned cells (this needs a bit of work).
+ Finally we'll add some thicker lines to divide it up, and hide the pink. Voila!
+ """, styNormal))
+ sty = TableStyle([
+ #
+# ('GRID', (0,0), (-1,-1), 0.25, colors.pink),
+ ('TOPPADDING', (0,0), (-1,-1), 3),
+
+ #span the 'period'
+ ('SPAN', (2,0), (5,0)),
+ #span the other column heads down 2 rows
+ ('SPAN', (0,0), (0,1)),
+ ('SPAN', (1,0), (1,1)),
+ ('SPAN', (6,0), (6,1)),
+ #span the 'north' and 'south' down 3 rows each
+ ('SPAN', (0,2), (0,4)),
+ ('SPAN', (0,5), (0,7)),
+
+ #top row headings are centred
+ ('ALIGN', (0,0), (-1,0), 'CENTER'),
+ #everything we span is vertically centred
+ #span the other column heads down 2 rows
+ ('VALIGN', (0,0), (0,1), 'MIDDLE'),
+ ('VALIGN', (1,0), (1,1), 'MIDDLE'),
+ ('VALIGN', (6,0), (6,1), 'MIDDLE'),
+ #span the 'north' and 'south' down 3 rows each
+ ('VALIGN', (0,2), (0,4), 'MIDDLE'),
+ ('VALIGN', (0,5), (0,7), 'MIDDLE'),
+
+ #numeric stuff right aligned
+ ('ALIGN', (2,1), (-1,-1), 'RIGHT'),
+
+ #draw lines carefully so as not to swipe through
+ #any of the 'spanned' cells
+ ('GRID', (1,2), (-1,-1), 1.0, colors.black),
+ ('BOX', (0,2), (0,4), 1.0, colors.black),
+ ('BOX', (0,5), (0,7), 1.0, colors.black),
+ ('BOX', (0,0), (0,1), 1.0, colors.black),
+ ('BOX', (1,0), (1,1), 1.0, colors.black),
+
+ ('BOX', (2,0), (5,0), 1.0, colors.black),
+ ('GRID', (2,1), (5,1), 1.0, colors.black),
+
+ ('BOX', (6,0), (6,1), 1.0, colors.black),
+
+ # do fatter boxes around some cells
+ ('BOX', (0,0), (-1,1), 2.0, colors.black),
+ ('BOX', (0,2), (-1,4), 2.0, colors.black),
+ ('BOX', (0,5), (-1,7), 2.0, colors.black),
+ ('BOX', (-1,0), (-1,-1), 2.0, colors.black),
+
+ ])
+
+ t = Table(self.getDataBlock(), colWidths=None, rowHeights=None, style=sty)
+ lst.append(t)
+
+ lst.append(Paragraph("""How cells get sized""", styleSheet['Heading1']))
+
+ lst.append(Paragraph("""So far the table has been auto-sized. This can be
+ computationally expensive, and can lead to yucky effects. Imagine a lot of
+ numbers, one of which goes to 4 figures - tha numeric column will be wider.
+ The best approach is to specify the column
+ widths where you know them, and let the system do the heights. Here we set some
+ widths - an inch for the text columns and half an inch for the numeric ones.
+ """, styNormal))
+
+ t = Table(self.getDataBlock(),
+ colWidths=(72,72,36,36,36,36,56),
+ rowHeights=None,
+ style=sty)
+ lst.append(t)
+
+ lst.append(Paragraph("""The auto-sized example 2 steps back demonstrates
+ one advanced feature of the sizing algorithm. In the table below,
+ the columns for Q1-Q4 should all be the same width. We've made
+ the text above it a bit longer than "Period". Note that this text
+ is technically in the 3rd column; on our first implementation this
+ was sized and column 3 was therefore quite wide. To get it right,
+ we ensure that any cells which span columns, or which are 'overwritten'
+ by cells which span columns, are assigned zero width in the cell
+ sizing. Thus, only the string 'Q1' and the numbers below it are
+ calculated in estimating the width of column 3, and the phrase
+ "What time of year?" is not used. However, row-spanned cells are
+ taken into account. ALL the cells in the leftmost column
+ have a vertical span (or are occluded by others which do)
+ but it can still work out a sane width for them.
+
+ """, styNormal))
+
+ data = self.getDataBlock()
+ data[0][2] = "Which time of year?"
+ #data[7][0] = Paragraph("Let's <b>really mess things up with a <i>paragraph</i>",styNormal)
+ t = Table(data,
+ #colWidths=(72,72,36,36,36,36,56),
+ rowHeights=None,
+ style=sty)
+ lst.append(t)
+
+ lst.append(Paragraph("""Paragraphs and unsizeable objects in table cells.""", styleSheet['Heading1']))
+
+ lst.append(Paragraph("""Paragraphs and other flowable objects make table
+ sizing much harder. In general the height of a paragraph is a function
+ of its width so you can't ask it how wide it wants to be - and the
+ REALLY wide all-on-one-line solution is rarely what is wanted. We
+ refer to Paragraphs and their kin as "unsizeable objects". In this example
+ we have set the widths of all but the first column. As you can see
+ it uses all the available space across the page for the first column.
+ Note also that this fairly large cell does NOT contribute to the
+ height calculation for its 'row'. Under the hood it is in the
+ same row as the second Spam, but this row gets a height based on
+ its own contents and not the cell with the paragraph.
+
+ """, styNormal))
+
+
+ data = self.getDataBlock()
+ data[5][0] = Paragraph("Let's <b>really mess things up</b> with a <i>paragraph</i>, whose height is a function of the width you give it.",styNormal)
+ t = Table(data,
+ colWidths=(None,72,36,36,36,36,56),
+ rowHeights=None,
+ style=sty)
+ lst.append(t)
+
+
+ lst.append(Paragraph("""This one demonstrates that our current algorithm
+ does not cover all cases :-( The height of row 0 is being driven by
+ the width of the para, which thinks it should fit in 1 column and not 4.
+ To really get this right would involve multiple passes through all the cells
+ applying rules until everything which can be sized is sized (possibly
+ backtracking), applying increasingly dumb and brutal
+ rules on each pass.
+ """, styNormal))
+ data = self.getDataBlock()
+ data[0][2] = Paragraph("Let's <b>really mess things up</b> with a <i>paragraph</i>.",styNormal)
+ data[5][0] = Paragraph("Let's <b>really mess things up</b> with a <i>paragraph</i>, whose height is a function of the width you give it.",styNormal)
+ t = Table(data,
+ colWidths=(None,72,36,36,36,36,56),
+ rowHeights=None,
+ style=sty)
+ lst.append(t)
+
+ lst.append(Paragraph("""To avoid these problems remember the golden rule
+ of ReportLab tables: (1) fix the widths if you can, (2) don't use
+ a paragraph when a string will do.
+ """, styNormal))
+
+ SimpleDocTemplate('test_table_layout.pdf', showBoundary=1).build(lst)
+
+def makeSuite():
+ return makeSuiteForClasses(TableTestCase)
+
+
+#noruntests
+if __name__ == "__main__":
+ unittest.TextTestRunner().run(makeSuite())
+ print 'saved test_table_layout.pdf'
+
+
\ No newline at end of file