[reportlab-users] RE: Infinite loop when table with Paragraph overflows a page
Henning von Bargen
reportlab-users@reportlab.com
Mon, 27 Oct 2003 08:04:31 +0100
This message is in MIME format. Since your mail reader does not understand
this format, some or all of this message may not be legible.
------_=_NextPart_000_01C39C58.9214FD10
Content-Type: text/plain
> From: David Fraser <davidf@sjsoft.com>
> To: reportlab-users@reportlab.com
> Subject: [reportlab-users] Infinite loop when table with Paragraph
overflows a page
> Reply-To: reportlab-users@reportlab.com
>
> Hi
>
> More table layout issues ...
> We've had trouble with the layout of a table with a large cell that
> doesn't fit in the page.
> If I run the attached test with numtests=6, the python process starts
> using up loads of memory and CPU, eventually growing to at least 500MB
> We don't neccessarily need to display all the data (as it doesn't fit),
> but it would be nice if there was some way to trap this condition and
> either (prefarably) truncate it or at least raise an error.
> The test basically produces a table with three columns, one of which has
> a paragraph in it with lots of text.
> Successive tables with more and more text are produced. If you stop off
> at numtests=5 (the default value), everything is fine.
>
> Any comments on where this might be in the code or how we can avoid it?
>
> Thanks
> David
>
David, I've seen a similar problem using tables.
It could be that the source of this is somewhere inside the Table
implementation:
def split(self, availWidth, availHeight):
self._calc(availWidth, availHeight)
if self.splitByRow:
if self._width>availWidth: return []
return self._splitRows(availHeight)
else:
raise NotImplementedError
Try commenting out the line
if self._width>availWidth: return []
I did this for my own LongTable implementation.
It will result in the table to be displayed (however,
information gets lost because not all data fits on the page).
This may also have something to do the line wrapping code
in the Paragraph class, especially if the paragraph contains
long words.
Henning
You might also test my LongTable class.
It works just like Table, but is designed for long tables.
<<longtables.py>>
Henning
------_=_NextPart_000_01C39C58.9214FD10
Content-Type: application/octet-stream;
name="longtables.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="longtables.py"
#copyright ReportLab Inc. 2000
#see license.txt for license details
#history =
http://cvs.sourceforge.net/cgi-bin/cvsweb.cgi/reportlab/platypus/tables.=
py?cvsroot=3Dreportlab
#$Header: /cvsroot/reportlab/reportlab/platypus/tables.py,v 1.66 =
2003/07/09 08:17:44 rgbecker Exp $
__version__=3D''' $Id: tables.py,v 1.66 2003/07/09 08:17:44 rgbecker =
Exp $ '''
__doc__=3D"""
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
color and weight of the lines (if any), and the font, alignment and =
padding of the text.
None values in the sequence of row heights or column widths, mean that =
the corresponding rows
or columns should be automatically sized.
All the cell values should be convertible to strings; embedded newline =
'\\n' characters
cause the value to wrap (ie are like a traditional linefeed).
See the test output from running this module as a script for a =
discussion of the method for constructing
tables and table styles.
"""
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.utils import fp_str
from reportlab.pdfbase import pdfmetrics
from reportlab.platypus.flowables import PageBreak
from reportlab.platypus.tables import CellStyle, LINECAPS, LINEJOINS, =
TableStyle
import operator, string
from types import TupleType, ListType, StringType
TableStyleType =3D type(TableStyle())
_SeqTypes =3D (TupleType, ListType)
def _rowLen(x):
return type(x) not in _SeqTypes and 1 or len(x)
class LongTable(Flowable):
def __init__(self, data, colWidths=3DNone, rowHeights=3DNone, =
style=3DNone,
repeatRows=3D0, repeatCols=3D0, splitByRow=3D1, =
emptyTableAction=3DNone):
#print "colWidths", colWidths
self.hAlign =3D 'CENTER'
self.vAlign =3D 'MIDDLE'
if type(data) not in _SeqTypes:
raise ValueError, "%s invalid data type" % self.identity()
self._nrows =3D nrows =3D len(data)
if nrows: self._ncols =3D ncols =3D max(map(_rowLen,data))
elif colWidths: ncols =3D len(colWidths)
else: ncols =3D 0
if not emptyTableAction: emptyTableAction =3D =
rl_config.emptyTableAction
if not (nrows and ncols):
if emptyTableAction=3D=3D'error':
raise ValueError, "%s must have at least a row and =
column" % self.identity()
elif emptyTableAction=3D=3D'indicate':
self.__class__ =3D Preformatted
global _emptyTableStyle
if '_emptyTableStyle' not in globals().keys():
_emptyTableStyle =3D =
ParagraphStyle('_emptyTableStyle')
_emptyTableStyle.textColor =3D colors.red
_emptyTableStyle.backColor =3D colors.yellow
Preformatted.__init__(self,'LongTable(%d,%d)' % =
(nrows,ncols), _emptyTableStyle)
elif emptyTableAction=3D=3D'ignore':
self.__class__ =3D Spacer
Spacer.__init__(self,0,0)
else:
raise ValueError, '%s bad emptyTableAction: "%s"' % =
(self.identity(),emptyTableAction)
return
if colWidths is None: colWidths =3D ncols*[None]
elif len(colWidths) !=3D ncols:
raise ValueError, "%s data error - %d columns in data but =
%d in grid" % (self.identity(),ncols, len(colWidths))
if rowHeights is None: rowHeights =3D nrows*[None]
elif len(rowHeights) !=3D nrows:
raise ValueError, "%s data error - %d rows in data but %d =
in grid" % (self.identity(),nrows, len(rowHeights))
for i in range(nrows):
if len(data[i]) !=3D ncols:
raise ValueError, "%s not enough data points in row =
%d!" % (self.identity(),i)
self._cellvalues =3D data
self._rowHeights =3D self._argH =3D rowHeights
self._colWidths =3D self._argW =3D colWidths
cellrows =3D []
for i in range(nrows):
cellcols =3D []
for j in range(ncols):
cellcols.append(CellStyle(`(i,j)`))
cellrows.append(cellcols)
self._cellStyles =3D cellrows
self._bkgrndcmds =3D []
self._linecmds =3D []
self._spanCmds =3D []
self.repeatRows =3D repeatRows
self.repeatCols =3D repeatCols
self.splitByRow =3D splitByRow
if style:
self.setStyle(style)
def __repr__(self):
"incomplete, but better than nothing"
r =3D self._rowHeights
c =3D self._colWidths
cv =3D self._cellvalues
import pprint, string
cv =3D pprint.pformat(cv)
cv =3D string.replace(cv, "\n", "\n ")
return "LongTable(\n rowHeights=3D%s,\n colWidths=3D%s,\n%s\n) =
# end table" % (r,c,cv)
def identity(self, maxLen=3D30):
'''Identify our selves as well as possible'''
vx =3D None
nr =3D getattr(self,'_nrows','unknown')
nc =3D getattr(self,'_ncols','unknown')
cv =3D self._cellvalues
if cv and 'unknown' not in (nr,nc):
b =3D 0
for i in xrange(nr):
for j in xrange(nc):
v =3D cv[i][j]
t =3D type(v)
if t in _SeqTypes or isinstance(v,Flowable):
if not t in _SeqTypes: v =3D (v,)
r =3D ''
for vij in v:
r =3D vij.identity(maxLen)
if r and r[-4:]!=3D'>...':
break
if r and r[-4:]!=3D'>...':
ix, jx, vx, b =3D i, j, r, 1
else:
v =3D v is None and '' or str(v)
ix, jx, vx =3D i, j, v
b =3D (vx and t is StringType) and 1 or 0
if maxLen: vx =3D vx[:maxLen]
if b: break
if b: break
if vx:
vx =3D ' with cell(%d,%d) containing\n%s' % =
(ix,jx,repr(vx))
else:
vx =3D '...'
return "<%s at %d %d rows x %s cols>%s" % =
(self.__class__.__name__, id(self), nr, nc, vx)
def _listCellGeom(self, V,w,s,W=3DNone,H=3DNone):
aW =3D w-s.leftPadding-s.rightPadding
t =3D 0
w =3D 0
canv =3D getattr(self,'canv',None)
for v in V:
vw, vh =3D v.wrapOn(canv,aW, 72000)
if W is not None: W.append(vw)
if H is not None: H.append(vh)
w =3D max(w,vw)
t =3D t + vh + v.getSpaceBefore()+v.getSpaceAfter()
return w, t - V[0].getSpaceBefore()-V[-1].getSpaceAfter()
def _calc_width(self):
#comments added by Andy to Robin's slightly
#terse variable names
#print "_calc_width"
W =3D self._argW #widths array
#print 'widths array =3D %s' % str(self._colWidths)
canv =3D getattr(self,'canv',None)
saved =3D None
if None in W: #some column widths are not given
if self._spanCmds:
colspans =3D self._colSpannedCells
else:
colspans =3D {}
## k =3D colspans.keys()
## k.sort()
## print 'the following cells are part of spanned ranges: =
%s' % k
W =3D W[:]
self._colWidths =3D W
while None in W:
j =3D W.index(None) #find first unspecified column
#print 'sizing column %d' % j
f =3D lambda x,j=3Dj: operator.getitem(x,j)
V =3D map(f,self._cellvalues) #values for this column
S =3D map(f,self._cellStyles) #styles for this column
w =3D 0
i =3D 0
=20
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 =3D 0.0
else:#work out size
t =3D self._elementWidth(v,s)
if t is None:
raise ValueError, "Flowable %s in =
cell(%d,%d) can't have auto width\n%s" % =
(v.identity(30),i,j,self.identity(30))
t =3D t + s.leftPadding+s.rightPadding
if t>w: w =3D t #record a new maximum
i =3D i + 1
#print 'max width for column %d is %0.2f' % (j, w)
W[j] =3D w
width =3D 0
self._colpositions =3D [0] #index -1 is right side =
boundary; we skip when processing cells
for w in W:
#print w, width
width =3D width + w
self._colpositions.append(width)
#print "final width", width
self._width =3D width
def _elementWidth(self,v,s):
t =3D type(v)
if t in _SeqTypes:
w =3D 0
for e in v:
ew =3D self._elementWidth(self,v)
if ew is None: return None
w =3D max(w,ew)
return w
elif isinstance(v,Flowable) and v._fixedWidth:
return v.width
else:
if t is not StringType: v =3D v is None and '' or str(v)
v =3D string.split(v, "\n")
return max(map(lambda a, b=3Ds.fontname, =
c=3Ds.fontsize,d=3Dpdfmetrics.stringWidth: d(a,b,c), v))
def _calc_height(self, availHeight):
#print "start of calc_heights, H=3D", self._argH, =
"availHeight=3D", availHeight
H =3D self._argH
W =3D self._argW
canv =3D getattr(self,'canv',None)
saved =3D None
#get a handy list of any cells which span rows.
#these should be ignored for sizing
if self._spanCmds:
spans =3D self._rowSpannedCells
else:
spans =3D {}
=20
cntcalc =3D 0
self._Hmax =3D len(H)
if None in H:
if canv: saved =3D canv._fontname, canv._fontsize, =
canv._leading
H =3D H[:] #make a copy as we'll change it
self._rowHeights =3D H
while None in H:
i =3D H.index(None)
# we can stop if we have filled up all available room
self._Hmax =3D i
heightUpToNow =3D reduce(operator.add, H[:i], 0)
if heightUpToNow > availHeight:
#print "breaking with Hmax=3D%d" % i
break
#print "calculating row#%d" % i
cntcalc +=3D 1
V =3D self._cellvalues[i] # values for row i
S =3D self._cellStyles[i] # styles for row i
h =3D 0
j =3D 0
for v, s, w in map(None, V, S, W): # value, style, =
width (lengths must match)
if spans.has_key((j, i)):
t =3D 0.0 # don't count it, it's either =
occluded or unreliable
else:
t =3D type(v)
if t in _SeqTypes or isinstance(v,Flowable):
if not t in _SeqTypes: v =3D (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 =3D s.fontname, s.fontsize, s.leading or 1.2*s.fontsize
dW,t =3D self._listCellGeom(v,w,s)
if canv: canv._fontname, canv._fontsize, =
canv._leading =3D saved
#print "leftpadding, rightpadding", =
s.leftPadding, s.rightPadding
dW =3D 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 =3D v is None and '' or str(v)
v =3D string.split(v, "\n")
t =3D s.leading*len(v)
t =3D t+s.bottomPadding+s.topPadding
if t>h: h =3D t #record a new maximum
j =3D j + 1
H[i] =3D h
self._Hmax =3D len(H)
#print "height calculated for %d rows" % cntcalc
=20
height =3D self._height =3D reduce(operator.add, =
H[:self._Hmax], 0)
#print "height, H", height, H
self._rowpositions =3D [height] # index 0 is actually =
topline; we skip when processing cells
for h in H[:self._Hmax]:
height =3D height - h
self._rowpositions.append(height)
assert abs(height)<1e-8, 'Internal height error'
#print "end of calc_heights, H=3D", H
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._hasVariWidthElements():
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()
=20
# calculate the full table height
#print 'during calc, self._colWidths=3D', self._colWidths
self._calc_height(availHeight)
# if the width has already been calculated, don't calculate =
again
# there's surely a better, more pythonic way to short circuit =
this FIXME FIXME
if hasattr(self,'_width_calculated_once'): return
self._width_calculated_once =3D 1
# calculate the full table width
self._calc_width()
=20
if self._spanCmds:
#now work out the actual rect for each spanned cell
#from the underlying grid
self._calcSpanRects()
def _hasVariWidthElements(self, upToRow=3DNone):
"""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 =3D 0
if upToRow is None: upToRow =3D self._nrows
for row in range(min(self._nrows, upToRow)):
for col in range(self._ncols):
value =3D self._cellvalues[row][col]
if not self._canGetWidth(value):
bad =3D 1
#raise Exception('Unsizable elements found at row =
%d column %d in table with content:\n %s' % (row, col, value))
return bad
def _canGetWidth(self, thing):
"Can we work out the width quickly?"
if type(thing) in (ListType, TupleType):
for elem in thing:
if not self._canGetWidth(elem):
return 0
return 1
elif isinstance(thing, Flowable):
return thing._fixedWidth # 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.
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 =3D 0
totalDefined =3D 0.0
numberUndefined =3D 0
for w in self._colWidths:
if w is None:
numberUndefined =3D numberUndefined + 1
else:
totalDefined =3D 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 =3D []
sizeable =3D []
unsizeable =3D []
for colNo in range(self._ncols):
if self._colWidths[colNo] is None:
siz =3D 1
for rowNo in range(self._nrows):
value =3D self._cellvalues[rowNo][colNo]
if not self._canGetWidth(value):
siz =3D 0
break
if siz:
sizeable.append(colNo)
else:
unsizeable.append(colNo)
else:
given.append(colNo)
if len(given) =3D=3D 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 =3D list(self._colWidths)
guessColWidth =3D (availWidth - totalDefined) / =
(len(unsizeable)+len(sizeable))
assert guessColWidth >=3D 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] =3D guessColWidth
for colNo in unsizeable:
newColWidths[colNo] =3D guessColWidth
self._colWidths =3D newColWidths
self._argW =3D newColWidths
if verbose: print 'new widths are:', self._colWidths
=20
def _calcSpanRanges(self):
"""Work out rects for tables which do row and column spanning.
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)
Any cell not in the key is not part of a spanned region
"""
spanRanges =3D {}
for row in range(self._nrows):
for col in range(self._ncols):
spanRanges[(col, row)] =3D (col, row, col, row)
for (cmd, start, stop) in self._spanCmds:
x0, y0 =3D start
x1, y1 =3D stop
#normalize
if x0 < 0: x0 =3D x0 + self._ncols
if x1 < 0: x1 =3D x1 + self._ncols
if y0 < 0: y0 =3D y0 + self._nrows
if y1 < 0: y1 =3D y1 + self._nrows
=20
if x0 > x1:
x0, x1 =3D x1, x0
if y0 > y1:
y0, y1 =3D y1, y0
# unset/clobber all the ones it
# overwrites
for y in range(y0, y1+1):
for x in range(x0, x1+1):
spanRanges[x, y] =3D None
# set the main entry =20
spanRanges[x0,y0] =3D (x0, y0, x1, y1)
## from pprint import pprint as pp
## pp(spanRanges)
self._spanRanges =3D 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 =3D {}
for (key, value) in spanRanges.items():
if value is None:
colSpannedCells[key] =3D 1
elif len(value) =3D=3D 4:
if value[0] =3D=3D value[2]:
#not colspanned
pass
else:
colSpannedCells[key] =3D 1
self._colSpannedCells =3D colSpannedCells
#ditto for row-spanned ones.
rowSpannedCells =3D {}
for (key, value) in spanRanges.items():
if value is None:
rowSpannedCells[key] =3D 1
elif len(value) =3D=3D 4:
if value[1] =3D=3D value[3]:
#not rowspanned
pass
else:
rowSpannedCells[key] =3D 1
self._rowSpannedCells =3D rowSpannedCells
=20
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,=20
self._spanRects shows the real coords for drawing:
(col, row) -> (x, y, width, height)
=20
for each cell. Any cell which 'does not exist' as another
has spanned over it will get a None entry on the right
"""
spanRects =3D {}
for (coord, value) in self._spanRanges.items():
if value is None:
spanRects[coord] =3D None
else:
col,row =3D coord
col0, row0, col1, row1 =3D value
x =3D self._colpositions[col0]
y =3D self._rowpositions[row1+1] # should I add 1 for =
bottom left?
width =3D self._colpositions[col1+1] - x
height =3D self._rowpositions[row0] - y
spanRects[coord] =3D (x, y, width, height)
=20
self._spanRects =3D spanRects
=20
def setStyle(self, tblstyle):
if type(tblstyle) is not TableStyleType:
tblstyle =3D TableStyle(tblstyle)
for cmd in tblstyle.getCommands():
self._addCommand(cmd)
for k,v in tblstyle._opts.items():
setattr(self,k,v)
def _addCommand(self,cmd):
if cmd[0] =3D=3D 'BACKGROUND':
self._bkgrndcmds.append(cmd)
elif cmd[0] =3D=3D 'SPAN':
self._spanCmds.append(cmd)
elif _isLineCommand(cmd):
# we expect op, start, stop, weight, colour, cap, dashes, =
join
cmd =3D tuple(cmd)
if len(cmd)<5: raise ValueError('bad line command =
'+str(cmd))
if len(cmd)<6: cmd =3D cmd+(1,)
else:
cap =3D cmd[5]
try:
if type(cap) is not type(int):
cap =3D LINECAPS[cap]
elif cap<0 or cap>2:
raise ValueError
cmd =3D cmd[:5]+(cap,)+cmd[6:]
except:
ValueError('Bad cap value %s in %s'%(cap,str(cmd)))
if len(cmd)<7: cmd =3D cmd+(None,)
if len(cmd)<8: cmd =3D cmd+(1,)
else:
join =3D cmd[7]
try:
if type(join) is not type(int):
join =3D LINEJOINS[cap]
elif join<0 or join>2:
raise ValueError
cmd =3D cmd[:7]+(join,)
except:
ValueError('Bad join value %s in =
%s'%(join,str(cmd)))
self._linecmds.append(cmd)
else:
(op, (sc, sr), (ec, er)), values =3D cmd[:3] , cmd[3:]
if sc < 0: sc =3D sc + self._ncols
if ec < 0: ec =3D ec + self._ncols
if sr < 0: sr =3D sr + self._nrows
if er < 0: er =3D er + self._nrows
for i in range(sr, er+1):
for j in range(sc, ec+1):
_setCellStyle(self._cellStyles, i, j, op, values)
def _drawLines(self):
ccap, cdash, cjoin =3D None, None, None
self.canv.saveState()
for op, (sc, sr), (ec, er), weight, color, cap, dash, join in =
self._linecmds:
if sc < 0: sc =3D sc + self._ncols
if ec < 0: ec =3D ec + self._ncols
if sr < 0: sr =3D sr + self._nrows
if er < 0: er =3D er + self._nrows
if ccap!=3Dcap:
self.canv.setLineCap(cap)
ccap =3D cap
getattr(self,_LineOpMap.get(op, '_drawUnknown' ))( (sc, =
sr), (ec, er), weight, color)
self.canv.restoreState()
self._curcolor =3D None
def _drawUnknown(self, (sc, sr), (ec, er), weight, color):
raise ValueError, "Unknown line command '%s'" % op
def _drawGrid(self, (sc, sr), (ec, er), weight, color):
self._drawBox( (sc, sr), (ec, er), weight, color)
self._drawInnerGrid( (sc, sr), (ec, er), weight, color)
def _drawBox(self, (sc, sr), (ec, er), weight, color):
self._drawHLines((sc, sr), (ec, sr), weight, color)
self._drawHLines((sc, er+1), (ec, er+1), weight, color)
self._drawVLines((sc, sr), (sc, er), weight, color)
self._drawVLines((ec+1, sr), (ec+1, er), weight, color)
def _drawInnerGrid(self, (sc, sr), (ec, er), weight, color):
self._drawHLines((sc, sr+1), (ec, er), weight, color)
self._drawVLines((sc+1, sr), (ec, er), weight, color)
def _prepLine(self, weight, color):
if color !=3D self._curcolor:
self.canv.setStrokeColor(color)
self._curcolor =3D color
if weight !=3D self._curweight:
self.canv.setLineWidth(weight)
self._curweight =3D weight
def _drawHLines(self, (sc, sr), (ec, er), weight, color):
ecp =3D self._colpositions[sc:ec+2]
rp =3D self._rowpositions[sr:er+1]
if len(ecp)<=3D1 or len(rp)<1: return
self._prepLine(weight, color)
scp =3D ecp[0]
ecp =3D ecp[-1]
for rowpos in rp:
self.canv.line(scp, rowpos, ecp, rowpos)
def _drawHLinesB(self, (sc, sr), (ec, er), weight, color):
self._drawHLines((sc, sr+1), (ec, er+1), weight, color)
def _drawVLines(self, (sc, sr), (ec, er), weight, color):
erp =3D self._rowpositions[sr:er+2]
cp =3D self._colpositions[sc:ec+1]
if len(erp)<=3D1 or len(cp)<1: return
self._prepLine(weight, color)
srp =3D erp[0]
erp =3D erp[-1]
for colpos in cp:
self.canv.line(colpos, srp, colpos, erp)
def _drawVLinesA(self, (sc, sr), (ec, er), weight, color):
self._drawVLines((sc+1, sr), (ec+1, er), weight, color)
def wrap(self, availWidth, availHeight):
#print "LongTable wrap", availWidth, availHeight
self._calc(availWidth, availHeight)
#nice and easy, since they are predetermined size
self.availWidth =3D availWidth
#print "wrap returning (%d,%d)" % (self._width, self._height)
return (self._width, self._height)
def onSplit(self,T,byRow=3D1):
'''
This method will be called when the Table is split.
Special purpose tables can override to do special stuff.
'''
pass
def _cr_0(self,n,cmds):
for c in cmds:
c =3D tuple(c)
(sc,sr), (ec,er) =3D c[1:3]
if sr>=3Dn: continue
if er>=3Dn: er =3D n-1
self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:])
def _cr_1_1(self,n,repeatRows, cmds):
for c in cmds:
c =3D tuple(c)
(sc,sr), (ec,er) =3D c[1:3]
if sr>=3D0 and sr>=3DrepeatRows and sr<n and er>=3D0 and =
er<n: continue
if sr>=3DrepeatRows and sr<n: sr=3DrepeatRows
elif sr>=3DrepeatRows and sr>=3Dn: sr=3Dsr+repeatRows-n
if er>=3DrepeatRows and er<n: er=3DrepeatRows
elif er>=3DrepeatRows and er>=3Dn: er=3Der+repeatRows-n
self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:])
def _cr_1_0(self,n,cmds):
for c in cmds:
c =3D tuple(c)
(sc,sr), (ec,er) =3D c[1:3]
if er>=3D0 and er<n: continue
if sr>=3D0 and sr<n: sr=3D0
if sr>=3Dn: sr =3D sr-n
if er>=3Dn: er =3D er-n
self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:])
def _splitRows(self,availHeight):
h =3D 0
n =3D 0
#print "in _splitRows, availHeight=3D%d, _rowHeights=3D%s" % =
(availHeight, self._rowHeights)
lim =3D len(self._rowHeights)
while n<self._Hmax:
hn =3D h + self._rowHeights[n]
if hn>availHeight: break
h =3D hn
n =3D n + 1
if n<=3Dself.repeatRows:
return []
if n=3D=3Dlim: return [self]
repeatRows =3D self.repeatRows
repeatCols =3D self.repeatCols
splitByRow =3D self.splitByRow
data =3D self._cellvalues
#we're going to split into two superRows
#R0 =3D Table( data[:n], self._argW, self._argH[:n],
R0 =3D LongTable( data[:n], self._colWidths, self._argH[:n],
repeatRows=3DrepeatRows, repeatCols=3DrepeatCols,
splitByRow=3DsplitByRow)
#copy the styles and commands
R0._cellStyles =3D self._cellStyles[:n]
A =3D []
# hack up the line commands
for op, (sc, sr), (ec, er), weight, color, cap, dash, join in =
self._linecmds:
if sc < 0: sc =3D sc + self._ncols
if ec < 0: ec =3D ec + self._ncols
if sr < 0: sr =3D sr + self._nrows
if er < 0: er =3D er + self._nrows
if op in ('BOX','OUTLINE','GRID'):
if sr<n and er>=3Dn:
# we have to split the BOX
A.append(('LINEABOVE',(sc,sr), (ec,sr), weight, =
color, cap, dash, join))
A.append(('LINEBEFORE',(sc,sr), (sc,er), weight, =
color, cap, dash, join))
A.append(('LINEAFTER',(ec,sr), (ec,er), weight, =
color, cap, dash, join))
A.append(('LINEBELOW',(sc,er), (ec,er), weight, =
color, cap, dash, join))
if op=3D=3D'GRID':
A.append(('LINEBELOW',(sc,n-1), (ec,n-1), =
weight, color, cap, dash, join))
A.append(('LINEABOVE',(sc,n), (ec,n), weight, =
color, cap, dash, join))
A.append(('INNERGRID',(sc,sr), (ec,er), weight, =
color, cap, dash, join))
else:
A.append((op,(sc,sr), (ec,er), weight, color, cap, =
dash, join))
elif op in ('INNERGRID','LINEABOVE'):
if sr<n and er>=3Dn:
A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, =
color, cap, dash, join))
A.append(('LINEABOVE',(sc,n), (ec,n), weight, =
color, cap, dash, join))
A.append((op,(sc,sr), (ec,er), weight, color, cap, =
dash, join))
elif op =3D=3D 'LINEBELOW':
if sr<n and er>=3D(n-1):
A.append(('LINEABOVE',(sc,n), (ec,n), weight, =
color, cap, dash, join))
A.append((op,(sc,sr), (ec,er), weight, color))
elif op =3D=3D 'LINEABOVE':
if sr<=3Dn and er>=3Dn:
A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, =
color, cap, dash, join))
A.append((op,(sc,sr), (ec,er), weight, color, cap, =
dash, join))
else:
A.append((op,(sc,sr), (ec,er), weight, color, cap, =
dash, join))
R0._cr_0(n,A)
R0._cr_0(n,self._bkgrndcmds)
if repeatRows:
#R1 =3D Table(data[:repeatRows]+data[n:],self._argW,
R1 =3D =
LongTable(data[:repeatRows]+data[n:],self._colWidths,
repeatRows=3DrepeatRows, repeatCols=3DrepeatCols,
splitByRow=3DsplitByRow)
R1._cellStyles =3D =
self._cellStyles[:repeatRows]+self._cellStyles[n:]
R1._cr_1_1(n,repeatRows,A)
R1._cr_1_1(n,repeatRows,self._bkgrndcmds)
else:
#R1 =3D Table(data[n:], self._argW, self._argH[n:],
R1 =3D LongTable(data[n:], self._colWidths,
repeatRows=3DrepeatRows, repeatCols=3DrepeatCols,
splitByRow=3DsplitByRow)
R1._cellStyles =3D self._cellStyles[n:]
R1._cr_1_0(n,A)
R1._cr_1_0(n,self._bkgrndcmds)
R0.hAlign =3D R1.hAlign =3D self.hAlign
R0.vAlign =3D R1.vAlign =3D self.vAlign
self.onSplit(R0)
self.onSplit(R1)
return [R0,R1]
def split(self, availWidth, availHeight):
#print "LongTable split"
self._calc(availWidth, availHeight)
if self.splitByRow:
# Wir brechen selbst dann nicht ab, wenn die Tabelle zu =
breit ist.
# Matrix-Tabellen die breiter und h=F6her als eine Seite =
sind,
# sind zur Zeit noch nicht implementiert.
# if self._width>availWidth: return []
return self._splitRows(availHeight)
else:
raise NotImplementedError
def draw(self):
self._curweight =3D self._curcolor =3D self._curcellstyle =3D =
None
self._drawBkgrnd()
self._drawLines()
if self._spanCmds =3D=3D []:
# old fashioned case, no spanning, steam on and do each =
cell
for row, rowstyle, rowpos, rowheight in map(None, =
self._cellvalues, self._cellStyles, self._rowpositions[1:], =
self._rowHeights):
for cellval, cellstyle, colpos, colwidth in map(None, =
row, rowstyle, self._colpositions[:-1], self._colWidths):
self._drawCell(cellval, cellstyle, (colpos, =
rowpos), (colwidth, rowheight))
else:
# we have some row or col spans, need a more complex =
algorithm
# to find the rect for each
for rowNo in range(self._nrows):
for colNo in range(self._ncols):
cellRect =3D self._spanRects[colNo, rowNo]
if cellRect is not None:
(x, y, width, height) =3D cellRect
cellval =3D self._cellvalues[rowNo][colNo]
cellstyle =3D self._cellStyles[rowNo][colNo]
self._drawCell(cellval, cellstyle, (x, y), =
(width, height))
=20
def _drawBkgrnd(self):
nrows =3D self._nrows
ncols =3D self._ncols
for cmd, (sc, sr), (ec, er), arg in self._bkgrndcmds:
if sc < 0: sc =3D sc + ncols
if ec < 0: ec =3D ec + ncols
if sr < 0: sr =3D sr + nrows
if er < 0: er =3D er + nrows
x0 =3D self._colpositions[sc]
y0 =3D self._rowpositions[sr]
x1 =3D self._colpositions[min(ec+1,ncols)]
y1 =3D self._rowpositions[min(er+1,nrows)]
w, h =3D x1-x0, y1-y0
canv =3D self.canv
if callable(arg):
apply(arg,(self,canv, x0, y0, w, h))
else:
canv.setFillColor(colors.toColor(arg))
canv.rect(x0, y0, w, h, stroke=3D0,fill=3D1)
def _drawCell(self, cellval, cellstyle, (colpos, rowpos), =
(colwidth, rowheight)):
if self._curcellstyle is not cellstyle:
cur =3D self._curcellstyle
if cur is None or cellstyle.color !=3D cur.color:
self.canv.setFillColor(cellstyle.color)
if cur is None or cellstyle.leading !=3D cur.leading or =
cellstyle.fontname !=3D cur.fontname or cellstyle.fontsize !=3D =
cur.fontsize:
self.canv.setFont(cellstyle.fontname, =
cellstyle.fontsize, cellstyle.leading)
self._curcellstyle =3D cellstyle
just =3D cellstyle.alignment
valign =3D cellstyle.valign
n =3D type(cellval)
if n in _SeqTypes or isinstance(cellval,Flowable):
if not n in _SeqTypes: cellval =3D (cellval,)
# we assume it's a list of Flowables
W =3D []
H =3D []
w, h =3D =
self._listCellGeom(cellval,colwidth,cellstyle,W=3DW, H=3DH)
if valign=3D=3D'TOP':
y =3D rowpos + rowheight - cellstyle.topPadding
elif valign=3D=3D'BOTTOM':
y =3D rowpos+cellstyle.bottomPadding + h
else:
y =3D =
rowpos+(rowheight+cellstyle.bottomPadding-cellstyle.topPadding+h)/2.0
y =3D y+cellval[0].getSpaceBefore()
for v, w, h in map(None,cellval,W,H):
if just=3D=3D'LEFT': x =3D colpos+cellstyle.leftPadding
elif just=3D=3D'RIGHT': x =3D =
colpos+colwidth-cellstyle.rightPadding - w
elif just in ('CENTRE', 'CENTER'):
x =3D =
colpos+(colwidth+cellstyle.leftPadding-cellstyle.rightPadding-w)/2.0
else:
raise ValueError, 'Invalid justification %s' % just
y =3D y - v.getSpaceBefore()
y =3D y - h
v.drawOn(self.canv,x,y)
y =3D y - v.getSpaceAfter()
else:
if just =3D=3D 'LEFT':
draw =3D self.canv.drawString
x =3D colpos + cellstyle.leftPadding
elif just in ('CENTRE', 'CENTER'):
draw =3D self.canv.drawCentredString
x =3D colpos + colwidth * 0.5
elif just =3D=3D 'RIGHT':
draw =3D self.canv.drawRightString
x =3D colpos + colwidth - cellstyle.rightPadding
else:
raise ValueError, 'Invalid justification %s' % just
if n is StringType: val =3D cellval
else: val =3D str(cellval)
vals =3D string.split(val, "\n")
n =3D len(vals)
leading =3D cellstyle.leading
fontsize =3D cellstyle.fontsize
if valign=3D=3D'BOTTOM':
y =3D rowpos + =
cellstyle.bottomPadding+n*leading-fontsize
elif valign=3D=3D'TOP':
y =3D rowpos + rowheight - cellstyle.topPadding - =
fontsize
elif valign=3D=3D'MIDDLE':
y =3D rowpos + (cellstyle.bottomPadding + =
rowheight-cellstyle.topPadding+(n-1)*leading)/2.0
else:
raise ValueError, "Bad valign: '%s'" % str(valign)
for v in vals:
draw(x, y, v)
y =3D y-leading
# for text,
# drawCentredString(self, x, y, text) where x is center
# drawRightString(self, x, y, text) where x is right
# drawString(self, x, y, text) where x is left
_LineOpMap =3D { 'GRID':'_drawGrid',
'BOX':'_drawBox',
'OUTLINE':'_drawBox',
'INNERGRID':'_drawInnerGrid',
'LINEBELOW':'_drawHLinesB',
'LINEABOVE':'_drawHLines',
'LINEBEFORE':'_drawVLines',
'LINEAFTER':'_drawVLinesA', }
LINECOMMANDS =3D _LineOpMap.keys()
def _isLineCommand(cmd):
return cmd[0] in LINECOMMANDS
def _setCellStyle(cellStyles, i, j, op, values):
#new =3D CellStyle('<%d, %d>' % (i,j), cellStyles[i][j])
#cellStyles[i][j] =3D new
## modify in place!!!
new =3D cellStyles[i][j]
if op =3D=3D 'FONT':
n =3D len(values)
new.fontname =3D values[0]
if n>1:
new.fontsize =3D values[1]
if n>2:
new.leading =3D values[2]
else:
new.leading =3D new.fontsize*1.2
elif op in ('FONTNAME', 'FACE'):
new.fontname =3D values[0]
elif op in ('SIZE', 'FONTSIZE'):
new.fontsize =3D values[0]
elif op =3D=3D 'LEADING':
new.leading =3D values[0]
elif op =3D=3D 'TEXTCOLOR':
new.color =3D colors.toColor(values[0], colors.Color(0,0,0))
elif op in ('ALIGN', 'ALIGNMENT'):
new.alignment =3D values[0]
elif op =3D=3D 'VALIGN':
new.valign =3D values[0]
elif op =3D=3D 'LEFTPADDING':
new.leftPadding =3D values[0]
elif op =3D=3D 'RIGHTPADDING':
new.rightPadding =3D values[0]
elif op =3D=3D 'TOPPADDING':
new.topPadding =3D values[0]
elif op =3D=3D 'BOTTOMPADDING':
new.bottomPadding =3D values[0]
GRID_STYLE =3D TableStyle(
[('GRID', (0,0), (-1,-1), 0.25, colors.black),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
BOX_STYLE =3D TableStyle(
[('BOX', (0,0), (-1,-1), 0.50, colors.black),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
LABELED_GRID_STYLE =3D TableStyle(
[('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
('BOX', (0,0), (-1,-1), 2, colors.black),
('LINEBELOW', (0,0), (-1,0), 2, colors.black),
('LINEAFTER', (0,0), (0,-1), 2, colors.black),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
COLORED_GRID_STYLE =3D TableStyle(
[('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
('BOX', (0,0), (-1,-1), 2, colors.red),
('LINEBELOW', (0,0), (-1,0), 2, colors.black),
('LINEAFTER', (0,0), (0,-1), 2, colors.black),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
LIST_STYLE =3D TableStyle(
[('LINEABOVE', (0,0), (-1,0), 2, colors.green),
('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black),
('LINEBELOW', (0,-1), (-1,-1), 2, colors.green),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
def test():
from reportlab.lib.units import inch
rowheights =3D (24, 16, 16, 16, 16)
rowheights2 =3D (24, 16, 16, 16, 30)
colwidths =3D (50, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32)
data =3D (
('', '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', 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, =
453, '1,344','2,843')
)
data2 =3D (
('', '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')
)
styleSheet =3D getSampleStyleSheet()
lst =3D []
lst.append(Paragraph("Tables", styleSheet['Heading1']))
lst.append(Paragraph(__doc__, styleSheet['BodyText']))
lst.append(Paragraph("The LongTables (shown in different styles =
below) were created using the following code:", =
styleSheet['BodyText']))
lst.append(Preformatted("""
colwidths =3D (50, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32)
rowheights =3D (24, 16, 16, 16, 16)
data =3D (
('', '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', 893, 912, '1,212', 643, 789, 159,
888, '1,298', 832, 453, '1,344','2,843')
)
t =3D LongTable(data, colwidths, rowheights)
""", styleSheet['Code'], dedent=3D4))
lst.append(Paragraph("""
You can then give the LongTable a TableStyle object to control its =
format. The first TableStyle used was
created as follows:
""", styleSheet['BodyText']))
lst.append(Preformatted("""
GRID_STYLE =3D TableStyle(
[('GRID', (0,0), (-1,-1), 0.25, colors.black),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
""", styleSheet['Code']))
lst.append(Paragraph("""
TableStyles are created by passing in a list of commands. There are =
two types of commands - line commands
and cell formatting commands. In all cases, the first three =
elements of a command are the command name,
the starting cell and the ending cell.
""", styleSheet['BodyText']))
lst.append(Paragraph("""
Line commands always follow this with the weight and color of the =
desired lines. Colors can be names,
or they can be specified as a (R,G,B) tuple, where R, G and B are =
floats and (0,0,0) is black. The line
command names are: GRID, BOX, OUTLINE, INNERGRID, LINEBELOW, =
LINEABOVE, LINEBEFORE
and LINEAFTER. BOX and OUTLINE are equivalent, and GRID is the =
equivalent of applying both BOX and
INNERGRID.
""", styleSheet['BodyText']))
lst.append(Paragraph("""
Cell formatting commands are:
""", styleSheet['BodyText']))
lst.append(Paragraph("""
FONT - takes fontname, fontsize and (optional) leading.
""", styleSheet['Definition']))
lst.append(Paragraph("""
TEXTCOLOR - takes a color name or (R,G,B) tuple.
""", styleSheet['Definition']))
lst.append(Paragraph("""
ALIGNMENT (or ALIGN) - takes one of LEFT, RIGHT and CENTRE (or =
CENTER).
""", styleSheet['Definition']))
lst.append(Paragraph("""
LEFTPADDING - defaults to 6.
""", styleSheet['Definition']))
lst.append(Paragraph("""
RIGHTPADDING - defaults to 6.
""", styleSheet['Definition']))
lst.append(Paragraph("""
BOTTOMPADDING - defaults to 3.
""", styleSheet['Definition']))
lst.append(Paragraph("""
A tablestyle is applied to a table by calling =
LongTable.setStyle(tablestyle).
""", styleSheet['BodyText']))
t =3D LongTable(data, colwidths, rowheights)
t.setStyle(GRID_STYLE)
lst.append(PageBreak())
lst.append(Paragraph("This is GRID_STYLE\n", =
styleSheet['BodyText']))
lst.append(t)
t =3D LongTable(data, colwidths, rowheights)
t.setStyle(BOX_STYLE)
lst.append(Paragraph("This is BOX_STYLE\n", =
styleSheet['BodyText']))
lst.append(t)
lst.append(Paragraph("""
It was created as follows:
""", styleSheet['BodyText']))
lst.append(Preformatted("""
BOX_STYLE =3D TableStyle(
[('BOX', (0,0), (-1,-1), 0.50, colors.black),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
""", styleSheet['Code']))
t =3D LongTable(data, colwidths, rowheights)
t.setStyle(LABELED_GRID_STYLE)
lst.append(Paragraph("This is LABELED_GRID_STYLE\n", =
styleSheet['BodyText']))
lst.append(t)
t =3D LongTable(data2, colwidths, rowheights2)
t.setStyle(LABELED_GRID_STYLE)
lst.append(Paragraph("This is LABELED_GRID_STYLE ILLUSTRATES =
EXPLICIT LINE SPLITTING WITH NEWLINE (different heights and data)\n", =
styleSheet['BodyText']))
lst.append(t)
lst.append(Paragraph("""
It was created as follows:
""", styleSheet['BodyText']))
lst.append(Preformatted("""
LABELED_GRID_STYLE =3D TableStyle(
[('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
('BOX', (0,0), (-1,-1), 2, colors.black),
('LINEBELOW', (0,0), (-1,0), 2, colors.black),
('LINEAFTER', (0,0), (0,-1), 2, colors.black),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
""", styleSheet['Code']))
lst.append(PageBreak())
t =3D LongTable(data, colwidths, rowheights)
t.setStyle(COLORED_GRID_STYLE)
lst.append(Paragraph("This is COLORED_GRID_STYLE\n", =
styleSheet['BodyText']))
lst.append(t)
lst.append(Paragraph("""
It was created as follows:
""", styleSheet['BodyText']))
lst.append(Preformatted("""
COLORED_GRID_STYLE =3D TableStyle(
[('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
('BOX', (0,0), (-1,-1), 2, colors.red),
('LINEBELOW', (0,0), (-1,0), 2, colors.black),
('LINEAFTER', (0,0), (0,-1), 2, colors.black),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
""", styleSheet['Code']))
t =3D LongTable(data, colwidths, rowheights)
t.setStyle(LIST_STYLE)
lst.append(Paragraph("This is LIST_STYLE\n", =
styleSheet['BodyText']))
lst.append(t)
lst.append(Paragraph("""
It was created as follows:
""", styleSheet['BodyText']))
lst.append(Preformatted("""
LIST_STYLE =3D TableStyle(
[('LINEABOVE', (0,0), (-1,0), 2, colors.green),
('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black),
('LINEBELOW', (0,-1), (-1,-1), 2, colors.green),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
""", styleSheet['Code']))
t =3D LongTable(data, colwidths, rowheights)
ts =3D TableStyle(
[('LINEABOVE', (0,0), (-1,0), 2, colors.green),
('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black),
('LINEBELOW', (0,-1), (-1,-1), 3, colors.green,'butt'),
('LINEBELOW', (0,-1), (-1,-1), 1, colors.white,'butt'),
('ALIGN', (1,1), (-1,-1), 'RIGHT'),
('TEXTCOLOR', (0,1), (0,-1), colors.red),
('BACKGROUND', (0,0), (-1,0), colors.Color(0,0.7,0.7))]
)
t.setStyle(ts)
lst.append(Paragraph("This is a custom style\n", =
styleSheet['BodyText']))
lst.append(t)
lst.append(Paragraph("""
It was created as follows:
""", styleSheet['BodyText']))
lst.append(Preformatted("""
ts =3D TableStyle(
[('LINEABOVE', (0,0), (-1,0), 2, colors.green),
('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black),
('LINEBELOW', (0,-1), (-1,-1), 3, colors.green,'butt'),
('LINEBELOW', (0,-1), (-1,-1), 1, colors.white,'butt'),
('ALIGN', (1,1), (-1,-1), 'RIGHT'),
('TEXTCOLOR', (0,1), (0,-1), colors.red),
('BACKGROUND', (0,0), (-1,0), colors.Color(0,0.7,0.7))]
)
""", styleSheet['Code']))
data =3D (
('', 'Jan\nCold', 'Feb\n', 'Mar\n','Apr\n','May\n', 'Jun\nHot', =
'Jul\n', 'Aug\nThunder', 'Sep\n', 'Oct\n', 'Nov\n', 'Dec\n'),
('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', 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, =
453, '1,344','2,843')
)
c =3D list(colwidths)
c[0] =3D None
c[8] =3D None
t =3D LongTable(data, c, [None]+list(rowheights[1:]))
t.setStyle(LIST_STYLE)
lst.append(Paragraph("""
This is a LIST_STYLE table with the first rowheight set to None =
ie automatic.
The top row cells are split at a newline '\\n' character. The =
first and August
column widths were also set to None.
""", styleSheet['BodyText']))
lst.append(t)
lst.append(Paragraph("""
The red numbers should be aligned LEFT & BOTTOM, the blue =
RIGHT & TOP
and the green CENTER & MIDDLE.
""", styleSheet['BodyText']))
XY =3D [['X00y', 'X01y', 'X02y', 'X03y', 'X04y'],
['X10y', 'X11y', 'X12y', 'X13y', 'X14y'],
['X20y', 'X21y', 'X22y', 'X23y', 'X24y'],
['X30y', 'X31y', 'X32y', 'X33y', 'X34y']]
t=3DLongTable(XY, 5*[0.6*inch], 4*[0.6*inch])
t.setStyle([('ALIGN',(1,1),(-2,-2),'LEFT'),
('TEXTCOLOR',(1,1),(-2,-2),colors.red),
('VALIGN',(0,0),(1,-1),'TOP'),
('ALIGN',(0,0),(1,-1),'RIGHT'),
('TEXTCOLOR',(0,0),(1,-1),colors.blue),
('ALIGN',(0,-1),(-1,-1),'CENTER'),
('VALIGN',(0,-1),(-1,-1),'MIDDLE'),
('TEXTCOLOR',(0,-1),(-1,-1),colors.green),
('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
('BOX', (0,0), (-1,-1), 0.25, colors.black),
])
lst.append(t)
data =3D [('alignment', 'align\012alignment'),
('bulletColor', 'bulletcolor\012bcolor'),
('bulletFontName', 'bfont\012bulletfontname'),
('bulletFontSize', 'bfontsize\012bulletfontsize'),
('bulletIndent', 'bindent\012bulletindent'),
('firstLineIndent', 'findent\012firstlineindent'),
('fontName', 'face\012fontname\012font'),
('fontSize', 'size\012fontsize'),
('leading', 'leading'),
('leftIndent', 'leftindent\012lindent'),
('rightIndent', 'rightindent\012rindent'),
('spaceAfter', 'spaceafter\012spacea'),
('spaceBefore', 'spacebefore\012spaceb'),
('textColor', 'fg\012textcolor\012color')]
t =3D LongTable(data)
t.setStyle([
('VALIGN',(0,0),(-1,-1),'TOP'),
('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
('BOX', (0,0), (-1,-1), 0.25, colors.black),
])
lst.append(t)
t =3D LongTable([ ('Attribute', 'Synonyms'),
('alignment', 'align, alignment'),
('bulletColor', 'bulletcolor, bcolor'),
('bulletFontName', 'bfont, bulletfontname'),
('bulletFontSize', 'bfontsize, bulletfontsize'),
('bulletIndent', 'bindent, bulletindent'),
('firstLineIndent', 'findent, firstlineindent'),
('fontName', 'face, fontname, font'),
('fontSize', 'size, fontsize'),
('leading', 'leading'),
('leftIndent', 'leftindent, lindent'),
('rightIndent', 'rightindent, rindent'),
('spaceAfter', 'spaceafter, spacea'),
('spaceBefore', 'spacebefore, spaceb'),
('textColor', 'fg, textcolor, color')])
t.repeatRows =3D 1
t.setStyle([
('FONT',(0,0),(-1,1),'Times-Bold',10,12),
('FONT',(0,1),(-1,-1),'Courier',8,8),
('VALIGN',(0,0),(-1,-1),'MIDDLE'),
('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
('BOX', (0,0), (-1,-1), 0.25, colors.black),
('BACKGROUND', (0, 0), (-1, 0), colors.green),
('BACKGROUND', (0, 1), (-1, -1), colors.pink),
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
('ALIGN', (0, 1), (0, -1), 'LEFT'),
('ALIGN', (-1, 1), (-1, -1), 'RIGHT'),
('FONT', (0, 0), (-1, 0), 'Times-Bold', 12),
('ALIGN', (1, 1), (1, -1), 'CENTER'),
])
lst.append(t)
lst.append(LongTable(XY,
style=3D[ ('FONT',(0,0),(-1,-1),'Times-Roman', 5,6),
('GRID', (0,0), (-1,-1), 0.25, colors.blue),]))
lst.append(LongTable(XY,
style=3D[ ('FONT',(0,0),(-1,-1),'Times-Roman', 10,12),
('GRID', (0,0), (-1,-1), 0.25, colors.black),]))
lst.append(LongTable(XY,
style=3D[ ('FONT',(0,0),(-1,-1),'Times-Roman', 20,24),
('GRID', (0,0), (-1,-1), 0.25, colors.red),]))
lst.append(PageBreak())
data=3D [['00', '01', '02', '03', '04'],
['10', '11', '12', '13', '14'],
['20', '21', '22', '23', '24'],
['30', '31', '32', '33', '34']]
t=3DLongTable(data,style=3D[
('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),
])
lst.append(t)
lst.append(Spacer(0,6))
for s in t.split(4*inch,30):
lst.append(s)
lst.append(Spacer(0,6))
lst.append(Spacer(0,6))
for s in t.split(4*inch,36):
lst.append(s)
lst.append(Spacer(0,6))
lst.append(Spacer(0,6))
for s in t.split(4*inch,56):
lst.append(s)
lst.append(Spacer(0,6))
import os, reportlab.platypus
I =3D =
Image(os.path.join(os.path.dirname(reportlab.platypus.__file__),'..','to=
ols','pythonpoint','demos','leftlogo.gif'))
I.drawHeight =3D 1.25*inch*I.drawHeight / I.drawWidth
I.drawWidth =3D 1.25*inch
#I.drawWidth =3D 9.25*inch #uncomment to see better messaging
P =3D Paragraph("<para align=3Dcenter spaceb=3D3>The <b>ReportLab =
Left <font color=3Dred>Logo</font></b> Image</para>", =
styleSheet["BodyText"])
data=3D [['A', 'B', 'C', Paragraph("<b>A pa<font =
color=3Dred>r</font>a<i>graph</i></b><super><font =
color=3Dyellow>1</font></super>",styleSheet["BodyText"]), 'D'],
['00', '01', '02', [I,P], '04'],
['10', '11', '12', [I,P], '14'],
['20', '21', '22', '23', '24'],
['30', '31', '32', '33', '34']]
t=3DLongTable(data,style=3D[('GRID',(1,1),(-2,-2),1,colors.green),
('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, 1), colors.pink),
('BACKGROUND', (1, 1), (1, 2), colors.lavender),
('BACKGROUND', (2, 2), (2, 3), colors.orange),
('BOX',(0,0),(-1,-1),2,colors.black),
('GRID',(0,0),(-1,-1),0.5,colors.black),
('VALIGN',(3,0),(3,0),'BOTTOM'),
('BACKGROUND',(3,0),(3,0),colors.limegreen),
('BACKGROUND',(3,1),(3,1),colors.khaki),
('ALIGN',(3,1),(3,1),'CENTER'),
('BACKGROUND',(3,2),(3,2),colors.beige),
('ALIGN',(3,2),(3,2),'LEFT'),
])
t._argW[3]=3D1.5*inch
lst.append(t)
# now for an attempt at column spanning.
lst.append(PageBreak())
colWidths =3D [24] * 5
rowHeight =3D [20] * 5
data=3D [['A', 'BBBBB', 'C', 'D', 'E'],
['00', '01', '02', '03', '04'],
['10', '11', '12', '13', '14'],
['20', '21', '22', '23', '24'],
['30', '31', '32', '33', '34']]
sty =3D [
('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 =20
('SPAN',(0,0), (0, 1)),
('BACKGROUND',(0,0),(0,0),colors.cyan),
]
t=3DLongTable(data,style=3Dsty, colWidths =3D [20] * 5, rowHeights =
=3D [20]*5)
lst.append(t)
=20
SimpleDocTemplate('longtables.pdf', showBoundary=3D1).build(lst)
if __name__ =3D=3D '__main__':
test()
------_=_NextPart_000_01C39C58.9214FD10--