1
#Copyright ReportLab Europe Ltd. 2000-2004
2
#see license.txt for license details
3
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/tables.py
4
__version__=''' $Id$ '''
7
Tables are created by passing the constructor a tuple of column widths, a tuple of row heights and the data in
8
row order. Drawing of the table can be controlled by using a TableStyle instance. This allows control of the
9
color and weight of the lines (if any), and the font, alignment and padding of the text.
11
None values in the sequence of row heights or column widths, mean that the corresponding rows
12
or columns should be automatically sized.
14
All the cell values should be convertible to strings; embedded newline '\\n' characters
15
cause the value to wrap (ie are like a traditional linefeed).
17
See the test output from running this module as a script for a discussion of the method for constructing
18
tables and table styles.
20
from reportlab.platypus.flowables import Flowable, Preformatted
21
from reportlab import rl_config
22
from reportlab.lib.styles import PropertySet, ParagraphStyle
23
from reportlab.lib import colors
24
from reportlab.lib.utils import fp_str
25
from reportlab.pdfbase import pdfmetrics
26
import operator, string
27
from types import TupleType, ListType, StringType
29
class CellStyle(PropertySet):
31
'fontname':'Times-Roman',
41
'background': (1,1,1),
45
LINECAPS={'butt':0,'round':1,'projecting':2,'squared':2}
46
LINEJOINS={'miter':0,'round':1,'bevel':2}
48
# experimental replacement
49
class CellStyle1(PropertySet):
50
fontname = "Times-Roman"
62
def __init__(self, name, parent=None):
64
if parent is not None:
66
def copy(self, result=None):
69
for name in dir(self):
70
setattr(result, name, gettattr(self, name))
72
CellStyle = CellStyle1
75
def __init__(self, cmds=None, parent=None, **kw):
76
#handle inheritance from parent first.
79
# copy the parents list at construction time
80
commands = commands + parent.getCommands()
81
self._opts = parent._opts
83
commands = commands + list(cmds)
89
self._cmds.append(cmd)
91
L = map(repr, self._cmds)
93
L = string.join(L, " \n")
94
return "TableStyle(\n%s\n) # end TableStyle" % L
95
def getCommands(self):
98
TableStyleType = type(TableStyle())
99
_SeqTypes = (TupleType, ListType)
102
return type(x) not in _SeqTypes and 1 or len(x)
104
def _calc_pc(V,avail):
105
'''check list V for percentage or * values
106
1) absolute values go through unchanged
107
2) percentages are used as weights for unconsumed space
108
3) if no None values were seen '*' weights are
109
set equally with unclaimed space
110
otherwise * weights are assigned as None'''
120
if type(v) is type(""):
125
elif v.endswith('%'):
147
v = n==0 and s/m or None
152
def _hLine(canvLine, scp, ecp, y, hBlocks, FUZZ=rl_config._FUZZ):
154
Draw horizontal lines; do not draw through regions specified in hBlocks
155
This also serves for vertical lines with a suitable canvLine
157
if hBlocks: hBlocks = hBlocks.get(y,None)
158
if not hBlocks or scp>=hBlocks[-1][1]-FUZZ or ecp<=hBlocks[0][0]+FUZZ:
159
canvLine(scp,y,ecp,y)
163
while scp<ecp-FUZZ and i<n:
165
if x1<=scp+FUZZ or x0>=ecp-FUZZ:
170
if i0>scp: canvLine(scp,y,i0,y)
172
if scp<ecp-FUZZ: canvLine(scp,y,ecp,y)
174
def _multiLine(scp,ecp,y,canvLine,ws,count):
175
offset = 0.5*(count-1)*ws
177
for idx in xrange(count):
178
canvLine(scp, y, ecp, y)
181
class Table(Flowable):
182
def __init__(self, data, colWidths=None, rowHeights=None, style=None,
183
repeatRows=0, repeatCols=0, splitByRow=1, emptyTableAction=None):
184
self.hAlign = 'CENTER'
185
self.vAlign = 'MIDDLE'
186
if type(data) not in _SeqTypes:
187
raise ValueError, "%s invalid data type" % self.identity()
188
self._nrows = nrows = len(data)
189
self._cellvalues = []
190
_seqCW = type(colWidths) in _SeqTypes
191
_seqRH = type(rowHeights) in _SeqTypes
192
if nrows: self._ncols = ncols = max(map(_rowLen,data))
193
elif colWidths and _seqCW: ncols = len(colWidths)
195
if not emptyTableAction: emptyTableAction = rl_config.emptyTableAction
196
if not (nrows and ncols):
197
if emptyTableAction=='error':
198
raise ValueError, "%s must have at least a row and column" % self.identity()
199
elif emptyTableAction=='indicate':
200
self.__class__ = Preformatted
201
global _emptyTableStyle
202
if '_emptyTableStyle' not in globals().keys():
203
_emptyTableStyle = ParagraphStyle('_emptyTableStyle')
204
_emptyTableStyle.textColor = colors.red
205
_emptyTableStyle.backColor = colors.yellow
206
Preformatted.__init__(self,'%s(%d,%d)' % (self.__class__.__name__,nrows,ncols), _emptyTableStyle)
207
elif emptyTableAction=='ignore':
208
self.__class__ = Spacer
209
Spacer.__init__(self,0,0)
211
raise ValueError, '%s bad emptyTableAction: "%s"' % (self.identity(),emptyTableAction)
214
self._cellvalues = data
215
if not _seqCW: colWidths = ncols*[colWidths]
216
elif len(colWidths) != ncols:
217
raise ValueError, "%s data error - %d columns in data but %d in grid" % (self.identity(),ncols, len(colWidths))
218
if not _seqRH: rowHeights = nrows*[rowHeights]
219
elif len(rowHeights) != nrows:
220
raise ValueError, "%s data error - %d rows in data but %d in grid" % (self.identity(),nrows, len(rowHeights))
221
for i in range(nrows):
222
if len(data[i]) != ncols:
223
raise ValueError, "%s not enough data points in row %d!" % (self.identity(),i)
224
self._rowHeights = self._argH = rowHeights
225
self._colWidths = self._argW = colWidths
227
for i in range(nrows):
229
for j in range(ncols):
230
cellcols.append(CellStyle(`(i,j)`))
231
cellrows.append(cellcols)
232
self._cellStyles = cellrows
234
self._bkgrndcmds = []
237
self.repeatRows = repeatRows
238
self.repeatCols = repeatCols
239
self.splitByRow = splitByRow
244
"incomplete, but better than nothing"
245
r = getattr(self,'_rowHeights','[unknown]')
246
c = getattr(self,'_colWidths','[unknown]')
247
cv = getattr(self,'_cellvalues','[unknown]')
248
import pprint, string
249
cv = pprint.pformat(cv)
250
cv = string.replace(cv, "\n", "\n ")
251
return "%s(\n rowHeights=%s,\n colWidths=%s,\n%s\n) # end table" % (self.__class__.__name__,r,c,cv)
253
def identity(self, maxLen=30):
254
'''Identify our selves as well as possible'''
256
nr = getattr(self,'_nrows','unknown')
257
nc = getattr(self,'_ncols','unknown')
258
cv = getattr(self,'_cellvalues',None)
259
if cv and 'unknown' not in (nr,nc):
265
if t in _SeqTypes or isinstance(v,Flowable):
266
if not t in _SeqTypes: v = (v,)
269
r = vij.identity(maxLen)
270
if r and r[-4:]!='>...':
272
if r and r[-4:]!='>...':
273
ix, jx, vx, b = i, j, r, 1
275
v = v is None and '' or str(v)
277
b = (vx and t is StringType) and 1 or 0
278
if maxLen: vx = vx[:maxLen]
282
vx = ' with cell(%d,%d) containing\n%s' % (ix,jx,repr(vx))
286
return "<%s at %d %d rows x %s cols>%s" % (self.__class__.__name__, id(self), nr, nc, vx)
288
def _listCellGeom(self, V,w,s,W=None,H=None,aH=72000):
289
aW = w-s.leftPadding-s.rightPadding
290
aH = aH - s.topPadding - s.bottomPadding
293
canv = getattr(self,'canv',None)
295
vw, vh = v.wrapOn(canv,aW, aH)
296
if W is not None: W.append(vw)
297
if H is not None: H.append(vh)
299
t = t + vh + v.getSpaceBefore()+v.getSpaceAfter()
300
return w, t - V[0].getSpaceBefore()-V[-1].getSpaceAfter()
302
def _calc_width(self,availWidth,W=None):
303
if getattr(self,'_width_calculated_once',None): return
304
#comments added by Andy to Robin's slightly terse variable names
305
if not W: W = _calc_pc(self._argW,availWidth) #widths array
306
if None in W: #some column widths are not given
307
canv = getattr(self,'canv',None)
309
colSpanCells = self._spanCmds and self._colSpanCells or ()
310
if W is self._argW: W = W[:]
312
j = W.index(None) #find first unspecified column
313
f = lambda x,j=j: operator.getitem(x,j)
314
V = map(f,self._cellvalues) #values for this column
315
S = map(f,self._cellStyles) #styles for this column
319
for v, s in map(None, V, S):
320
#if the current cell is part of a spanned region,
322
if (j, i) in colSpanCells:
325
t = self._elementWidth(v,s)
327
raise ValueError, "Flowable %s in cell(%d,%d) can't have auto width\n%s" % (v.identity(30),i,j,self.identity(30))
328
t = t + s.leftPadding+s.rightPadding
329
if t>w: w = t #record a new maximum
336
self._colpositions = [0] #index -1 is right side boundary; we skip when processing cells
339
self._colpositions.append(width)
342
self._width_calculated_once = 1
344
def _elementWidth(self,v,s):
349
ew = self._elementWidth(self,v)
350
if ew is None: return None
353
elif isinstance(v,Flowable) and v._fixedWidth:
356
if t is not StringType: v = v is None and '' or str(v)
357
v = string.split(v, "\n")
358
return max(map(lambda a, b=s.fontname, c=s.fontsize,d=pdfmetrics.stringWidth: d(a,b,c), v))
360
def _calc_height(self, availHeight, availWidth, H=None, W=None):
363
if not W: W = _calc_pc(self._argW,availWidth) #widths array
366
longTable = getattr(self,'_longTableOptimize',None)
369
canv = getattr(self,'canv',None)
371
#get a handy list of any cells which span rows. should be ignored for sizing
373
rowSpanCells = self._rowSpanCells
374
colSpanCells = self._colSpanCells
375
spanRanges = self._spanRanges
376
colpositions = self._colpositions
378
rowSpanCells = colSpanCells = ()
379
if canv: saved = canv._fontname, canv._fontsize, canv._leading
380
H = H[:] #make a copy as we'll change it
386
height = reduce(operator.add, H[:i], 0)
387
# we can stop if we have filled up all available room
388
if height > availHeight: break
389
V = self._cellvalues[i] # values for row i
390
S = self._cellStyles[i] # styles for row i
393
for v, s, w in map(None, V, S, W): # value, style, width (lengths must match)
395
if ji in rowSpanCells:
396
t = 0.0 # don't count it, it's either occluded or unreliable
399
if t in _SeqTypes or isinstance(v,Flowable):
400
if not t in _SeqTypes: v = (v,)
402
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))
403
if canv: canv._fontname, canv._fontsize, canv._leading = s.fontname, s.fontsize, s.leading or 1.2*s.fontsize
404
if ji in colSpanCells:
406
w = max(colpositions[t[2]+1]-colpositions[t[0]],w)
407
dW,t = self._listCellGeom(v,w,s)
408
if canv: canv._fontname, canv._fontsize, canv._leading = saved
409
dW = dW + s.leftPadding + s.rightPadding
410
if not rl_config.allowTableBoundsErrors and dW>w:
411
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))
413
if t is not StringType:
414
v = v is None and '' or str(v)
415
v = string.split(v, "\n")
417
t = t+s.bottomPadding+s.topPadding
418
if t>h: h = t #record a new maximum
421
if None not in H: hmax = lim
423
height = self._height = reduce(operator.add, H[:hmax], 0)
424
self._rowpositions = [height] # index 0 is actually topline; we skip when processing cells
427
self._rowpositions.append(height)
428
assert abs(height)<1e-8, 'Internal height error'
431
def _calc(self, availWidth, availHeight):
432
#if hasattr(self,'_width'): return
434
#in some cases there are unsizable things in
435
#cells. If so, apply a different algorithm
436
#and assign some withs in a dumb way.
437
#this CHANGES the widths array.
438
if (None in self._colWidths or '*' in self._colWidths) and self._hasVariWidthElements():
439
W = self._calcPreliminaryWidths(availWidth) #widths
443
# need to know which cells are part of spanned
444
# ranges, so _calc_height and _calc_width can ignore them
447
self._calcSpanRanges()
448
if None in self._argH:
449
self._calc_width(availWidth,W=W)
451
# calculate the full table height
452
self._calc_height(availHeight,availWidth,W=W)
454
# calculate the full table width
455
self._calc_width(availWidth,W=W)
458
#now work out the actual rect for each spanned cell from the underlying grid
459
self._calcSpanRects()
461
def _hasVariWidthElements(self, upToRow=None):
462
"""Check for flowables in table cells and warn up front.
464
Allow a couple which we know are fixed size such as
465
images and graphics."""
467
if upToRow is None: upToRow = self._nrows
468
for row in range(min(self._nrows, upToRow)):
469
for col in range(self._ncols):
470
value = self._cellvalues[row][col]
471
if not self._canGetWidth(value):
473
#raise Exception('Unsizable elements found at row %d column %d in table with content:\n %s' % (row, col, value))
476
def _canGetWidth(self, thing):
477
"Can we work out the width quickly?"
478
if type(thing) in (ListType, TupleType):
480
if not self._canGetWidth(elem):
483
elif isinstance(thing, Flowable):
484
return thing._fixedWidth # must loosen this up
485
else: #string, number, None etc.
486
#anything else gets passed to str(...)
487
# so should be sizable
490
def _calcPreliminaryWidths(self, availWidth):
491
"""Fallback algorithm for when main one fails.
493
Where exact width info not given but things like
494
paragraphs might be present, do a preliminary scan
495
and assign some sensible values - just divide up
496
all unsizeable columns by the remaining space."""
498
W = _calc_pc(self._argW,availWidth) #widths array
504
numberUndefined = numberUndefined + 1
506
totalDefined = totalDefined + w
507
if verbose: print 'prelim width calculation. %d columns, %d undefined width, %0.2f units remain' % (
508
self._ncols, numberUndefined, availWidth - totalDefined)
510
#check columnwise in each None column to see if they are sizable.
514
for colNo in range(self._ncols):
517
for rowNo in range(self._nrows):
518
value = self._cellvalues[rowNo][colNo]
519
if not self._canGetWidth(value):
523
sizeable.append(colNo)
525
unsizeable.append(colNo)
528
if len(given) == self._ncols:
530
if verbose: print 'predefined width: ',given
531
if verbose: print 'uncomputable width: ',unsizeable
532
if verbose: print 'computable width: ',sizeable
534
#how much width is left:
535
# on the next iteration we could size the sizeable ones, for now I'll just
536
# divide up the space
537
newColWidths = list(W)
538
guessColWidth = (availWidth - totalDefined) / (len(unsizeable)+len(sizeable))
539
assert guessColWidth >= 0, "table is too wide already, cannot choose a sane width for undefined columns"
540
if verbose: print 'assigning width %0.2f to all undefined columns' % guessColWidth
541
for colNo in sizeable:
542
newColWidths[colNo] = guessColWidth
543
for colNo in unsizeable:
544
newColWidths[colNo] = guessColWidth
546
if verbose: print 'new widths are:', newColWidths
547
self._argW = self._colWidths = newColWidths
550
def _calcSpanRanges(self):
551
"""Work out rects for tables which do row and column spanning.
553
This creates some mappings to let the later code determine
554
if a cell is part of a "spanned" range.
555
self._spanRanges shows the 'coords' in integers of each
556
'cell range', or None if it was clobbered:
557
(col, row) -> (col0, row0, col1, row1)
559
Any cell not in the key is not part of a spanned region
561
self._spanRanges = spanRanges = {}
562
for x in xrange(self._ncols):
563
for y in xrange(self._nrows):
564
spanRanges[x,y] = (x, y, x, y)
565
self._colSpanCells = []
566
self._rowSpanCells = []
567
csa = self._colSpanCells.append
568
rsa = self._rowSpanCells.append
569
for (cmd, start, stop) in self._spanCmds:
574
if x0 < 0: x0 = x0 + self._ncols
575
if x1 < 0: x1 = x1 + self._ncols
576
if y0 < 0: y0 = y0 + self._nrows
577
if y1 < 0: y1 = y1 + self._nrows
578
if x0 > x1: x0, x1 = x1, x0
579
if y0 > y1: y0, y1 = y1, y0
584
for y in xrange(y0, y1+1):
585
for x in xrange(x0,x1+1):
589
for y in xrange(y0, y1+1):
590
for x in xrange(x0,x1+1):
593
for y in xrange(y0, y1+1):
594
for x in xrange(x0,x1+1):
595
spanRanges[x,y] = None
597
spanRanges[x0,y0] = (x0, y0, x1, y1)
599
def _calcSpanRects(self):
600
"""Work out rects for tables which do row and column spanning.
602
Based on self._spanRanges, which is already known,
603
and the widths which were given or previously calculated,
604
self._spanRects shows the real coords for drawing:
605
(col, row) -> (x, y, width, height)
607
for each cell. Any cell which 'does not exist' as another
608
has spanned over it will get a None entry on the right
610
if getattr(self,'_spanRects',None): return
611
colpositions = self._colpositions
612
rowpositions = self._rowpositions
613
self._spanRects = spanRects = {}
614
self._vBlocks = vBlocks = {}
615
self._hBlocks = hBlocks = {}
616
for (coord, value) in self._spanRanges.items():
618
spanRects[coord] = None
621
col0, row0, col1, row1 = value
623
for _ in xrange(col0+1,col1+1):
624
vBlocks.setdefault(colpositions[_],[]).append((rowpositions[row1+1],rowpositions[row0]))
626
for _ in xrange(row0+1,row1+1):
627
hBlocks.setdefault(rowpositions[_],[]).append((colpositions[col0],colpositions[col1+1]))
628
x = colpositions[col0]
629
y = rowpositions[row1+1]
630
width = colpositions[col1+1] - x
631
height = rowpositions[row0] - y
632
spanRects[coord] = (x, y, width, height)
634
for _ in hBlocks, vBlocks:
635
for value in _.values():
638
def setStyle(self, tblstyle):
639
if type(tblstyle) is not TableStyleType:
640
tblstyle = TableStyle(tblstyle)
641
for cmd in tblstyle.getCommands():
642
self._addCommand(cmd)
643
for k,v in tblstyle._opts.items():
646
def _addCommand(self,cmd):
647
if cmd[0] in ('BACKGROUND','ROWBACKGROUNDS','COLBACKGROUNDS'):
648
self._bkgrndcmds.append(cmd)
649
elif cmd[0] == 'SPAN':
650
self._spanCmds.append(cmd)
651
elif _isLineCommand(cmd):
652
# we expect op, start, stop, weight, colour, cap, dashes, join
654
if len(cmd)<5: raise ValueError('bad line command '+str(cmd))
656
#determine line cap value at position 5. This can be string or numeric.
662
if type(cap) is not type(int):
666
cmd = cmd[:5]+(cap,)+cmd[6:]
668
ValueError('Bad cap value %s in %s'%(cap,str(cmd)))
669
#dashes at index 6 - this is a dash array:
670
if len(cmd)<7: cmd = cmd+(None,)
672
#join mode at index 7 - can be string or numeric, look up as for caps
673
if len(cmd)<8: cmd = cmd+(1,)
677
if type(join) is not type(int):
678
join = LINEJOINS[cap]
679
elif join<0 or join>2:
681
cmd = cmd[:7]+(join,)
683
ValueError('Bad join value %s in %s'%(join,str(cmd)))
685
#linecount at index 8. Default is 1, set to 2 for double line.
688
cmd = cmd + (lineCount,)
691
assert lineCount >= 1
692
#linespacing at index 9. Not applicable unless 2+ lines, defaults to line
693
#width so you get a visible gap between centres
694
if len(cmd)<10: cmd = cmd + (cmd[3],)
696
assert len(cmd) == 10
698
self._linecmds.append(cmd)
700
(op, (sc, sr), (ec, er)), values = cmd[:3] , cmd[3:]
701
if sc < 0: sc = sc + self._ncols
702
if ec < 0: ec = ec + self._ncols
703
if sr < 0: sr = sr + self._nrows
704
if er < 0: er = er + self._nrows
705
for i in range(sr, er+1):
706
for j in range(sc, ec+1):
707
_setCellStyle(self._cellStyles, i, j, op, values)
709
def _drawLines(self):
710
ccap, cdash, cjoin = None, None, None
711
self.canv.saveState()
712
for op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space in self._linecmds:
713
if type(sr) is type('') and sr.startswith('split'): continue
714
if sc < 0: sc = sc + self._ncols
715
if ec < 0: ec = ec + self._ncols
716
if sr < 0: sr = sr + self._nrows
717
if er < 0: er = er + self._nrows
718
if cap!=None and ccap!=cap:
719
self.canv.setLineCap(cap)
721
getattr(self,_LineOpMap.get(op, '_drawUnknown' ))( (sc, sr), (ec, er), weight, color, count, space)
722
self.canv.restoreState()
723
self._curcolor = None
725
def _drawUnknown(self, (sc, sr), (ec, er), weight, color, count, space):
726
raise ValueError, "Unknown line command '%s'" % op
728
def _drawGrid(self, (sc, sr), (ec, er), weight, color, count, space):
729
self._drawBox( (sc, sr), (ec, er), weight, color, count, space)
730
self._drawInnerGrid( (sc, sr), (ec, er), weight, color, count, space)
732
def _drawBox(self, (sc, sr), (ec, er), weight, color, count, space):
733
self._drawHLines((sc, sr), (ec, sr), weight, color, count, space)
734
self._drawHLines((sc, er+1), (ec, er+1), weight, color, count, space)
735
self._drawVLines((sc, sr), (sc, er), weight, color, count, space)
736
self._drawVLines((ec+1, sr), (ec+1, er), weight, color, count, space)
738
def _drawInnerGrid(self, (sc, sr), (ec, er), weight, color, count, space):
739
self._drawHLines((sc, sr+1), (ec, er), weight, color, count, space)
740
self._drawVLines((sc+1, sr), (ec, er), weight, color, count, space)
742
def _prepLine(self, weight, color):
743
if color != self._curcolor:
744
self.canv.setStrokeColor(color)
745
self._curcolor = color
746
if weight != self._curweight:
747
self.canv.setLineWidth(weight)
748
self._curweight = weight
750
def _drawHLines(self, (sc, sr), (ec, er), weight, color, count, space):
751
ecp = self._colpositions[sc:ec+2]
752
rp = self._rowpositions[sr:er+1]
753
if len(ecp)<=1 or len(rp)<1: return
754
self._prepLine(weight, color)
757
hBlocks = getattr(self,'_hBlocks',{})
758
canvLine = self.canv.line
761
_hLine(canvLine, scp, ecp, y, hBlocks)
763
lf = lambda x0,y0,x1,y1,canvLine=canvLine, ws=weight+space, count=count: _multiLine(x0,x1,y0,canvLine,ws,count)
765
_hLine(lf, scp, ecp, y, hBlocks)
767
def _drawHLinesB(self, (sc, sr), (ec, er), weight, color, count, space):
768
self._drawHLines((sc, sr+1), (ec, er+1), weight, color, count, space)
770
def _drawVLines(self, (sc, sr), (ec, er), weight, color, count, space):
771
erp = self._rowpositions[sr:er+2]
772
cp = self._colpositions[sc:ec+1]
773
if len(erp)<=1 or len(cp)<1: return
774
self._prepLine(weight, color)
777
vBlocks = getattr(self,'_vBlocks',{})
778
canvLine = lambda y0, x0, y1, x1, _line=self.canv.line: _line(x0,y0,x1,y1)
781
_hLine(canvLine, erp, srp, x, vBlocks)
783
lf = lambda x0,y0,x1,y1,canvLine=canvLine, ws=weight+space, count=count: _multiLine(x0,x1,y0,canvLine,ws,count)
785
_hLine(lf, erp, srp, x, vBlocks)
787
def _drawVLinesA(self, (sc, sr), (ec, er), weight, color, count, space):
788
self._drawVLines((sc+1, sr), (ec+1, er), weight, color, count, space)
790
def wrap(self, availWidth, availHeight):
791
self._calc(availWidth, availHeight)
792
#nice and easy, since they are predetermined size
793
self.availWidth = availWidth
794
return (self._width, self._height)
796
def onSplit(self,T,byRow=1):
798
This method will be called when the Table is split.
799
Special purpose tables can override to do special stuff.
803
def _cr_0(self,n,cmds):
806
(sc,sr), (ec,er) = c[1:3]
809
self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:])
811
def _cr_1_1(self,n,repeatRows, cmds):
814
(sc,sr), (ec,er) = c[1:3]
815
if sr in ('splitfirst','splitlast'): self._addCommand(c)
817
if sr>=0 and sr>=repeatRows and sr<n and er>=0 and er<n: continue
818
if sr>=repeatRows and sr<n: sr=repeatRows
819
elif sr>=repeatRows and sr>=n: sr=sr+repeatRows-n
820
if er>=repeatRows and er<n: er=repeatRows
821
elif er>=repeatRows and er>=n: er=er+repeatRows-n
822
self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:])
824
def _cr_1_0(self,n,cmds):
827
(sc,sr), (ec,er) = c[1:3]
828
if sr in ('splitfirst','splitlast'): self._addCommand(c)
830
if er>=0 and er<n: continue
831
if sr>=0 and sr<n: sr=0
834
self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:])
836
def _splitRows(self,availHeight):
839
lim = len(self._rowHeights)
841
hn = h + self._rowHeights[n]
842
if hn>availHeight: break
846
if n<=self.repeatRows:
849
if n==lim: return [self]
851
repeatRows = self.repeatRows
852
repeatCols = self.repeatCols
853
splitByRow = self.splitByRow
854
data = self._cellvalues
856
#we're going to split into two superRows
857
#R0 = slelf.__class__( data[:n], self._argW, self._argH[:n],
858
R0 = self.__class__( data[:n], self._colWidths, self._argH[:n],
859
repeatRows=repeatRows, repeatCols=repeatCols,
860
splitByRow=splitByRow)
862
#copy the styles and commands
863
R0._cellStyles = self._cellStyles[:n]
866
# hack up the line commands
867
for op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space in self._linecmds:
868
if type(sr)is type('') and sr.startswith('split'):
869
A.append((op,(sc,sr), (ec,sr), weight, color, cap, dash, join, count, space))
872
elif sr=='splitfirst':
876
if sc < 0: sc = sc + self._ncols
877
if ec < 0: ec = ec + self._ncols
878
if sr < 0: sr = sr + self._nrows
879
if er < 0: er = er + self._nrows
881
if op in ('BOX','OUTLINE','GRID'):
883
# we have to split the BOX
884
A.append(('LINEABOVE',(sc,sr), (ec,sr), weight, color, cap, dash, join, count, space))
885
A.append(('LINEBEFORE',(sc,sr), (sc,er), weight, color, cap, dash, join, count, space))
886
A.append(('LINEAFTER',(ec,sr), (ec,er), weight, color, cap, dash, join, count, space))
887
A.append(('LINEBELOW',(sc,er), (ec,er), weight, color, cap, dash, join, count, space))
889
A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space))
890
A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space))
891
A.append(('INNERGRID',(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
893
A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
894
elif op in ('INNERGRID','LINEABOVE'):
896
A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space))
897
A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space))
898
A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
899
elif op == 'LINEBELOW':
900
if sr<n and er>=(n-1):
901
A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space))
902
A.append((op,(sc,sr), (ec,er), weight, color))
903
elif op == 'LINEABOVE':
905
A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space))
906
A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
908
A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space))
911
R0._cr_0(n,self._bkgrndcmds)
914
#R1 = slelf.__class__(data[:repeatRows]+data[n:],self._argW,
915
R1 = self.__class__(data[:repeatRows]+data[n:],self._colWidths,
916
self._argH[:repeatRows]+self._argH[n:],
917
repeatRows=repeatRows, repeatCols=repeatCols,
918
splitByRow=splitByRow)
919
R1._cellStyles = self._cellStyles[:repeatRows]+self._cellStyles[n:]
920
R1._cr_1_1(n,repeatRows,A)
921
R1._cr_1_1(n,repeatRows,self._bkgrndcmds)
923
#R1 = slelf.__class__(data[n:], self._argW, self._argH[n:],
924
R1 = self.__class__(data[n:], self._colWidths, self._argH[n:],
925
repeatRows=repeatRows, repeatCols=repeatCols,
926
splitByRow=splitByRow)
927
R1._cellStyles = self._cellStyles[n:]
929
R1._cr_1_0(n,self._bkgrndcmds)
932
R0.hAlign = R1.hAlign = self.hAlign
933
R0.vAlign = R1.vAlign = self.vAlign
938
def split(self, availWidth, availHeight):
939
self._calc(availWidth, availHeight)
941
if not rl_config.allowTableBoundsErrors and self._width>availWidth: return []
942
return self._splitRows(availHeight)
944
raise NotImplementedError
947
self._curweight = self._curcolor = self._curcellstyle = None
949
if self._spanCmds == []:
950
# old fashioned case, no spanning, steam on and do each cell
951
for row, rowstyle, rowpos, rowheight in map(None, self._cellvalues, self._cellStyles, self._rowpositions[1:], self._rowHeights):
952
for cellval, cellstyle, colpos, colwidth in map(None, row, rowstyle, self._colpositions[:-1], self._colWidths):
953
self._drawCell(cellval, cellstyle, (colpos, rowpos), (colwidth, rowheight))
955
# we have some row or col spans, need a more complex algorithm
956
# to find the rect for each
957
for rowNo in range(self._nrows):
958
for colNo in range(self._ncols):
959
cellRect = self._spanRects[colNo, rowNo]
960
if cellRect is not None:
961
(x, y, width, height) = cellRect
962
cellval = self._cellvalues[rowNo][colNo]
963
cellstyle = self._cellStyles[rowNo][colNo]
964
self._drawCell(cellval, cellstyle, (x, y), (width, height))
968
def _drawBkgrnd(self):
971
for cmd, (sc, sr), (ec, er), arg in self._bkgrndcmds:
972
if sc < 0: sc = sc + ncols
973
if ec < 0: ec = ec + ncols
974
if sr < 0: sr = sr + nrows
975
if er < 0: er = er + nrows
976
x0 = self._colpositions[sc]
977
y0 = self._rowpositions[sr]
978
x1 = self._colpositions[min(ec+1,ncols)]
979
y1 = self._rowpositions[min(er+1,nrows)]
983
apply(arg,(self,canv, x0, y0, w, h))
984
elif cmd == 'ROWBACKGROUNDS':
985
#Need a list of colors to cycle through. The arguments
986
#might be already colours, or convertible to colors, or
987
# None, or the string 'None'.
988
#It's very common to alternate a pale shade with None.
989
#print 'rowHeights=', self._rowHeights
990
colorCycle = map(colors.toColorOrNone, arg)
991
count = len(colorCycle)
992
rowCount = er - sr + 1
993
for i in range(rowCount):
994
color = colorCycle[i%count]
995
h = self._rowHeights[sr + i]
997
canv.setFillColor(color)
998
canv.rect(x0, y0, w, -h, stroke=0,fill=1)
999
#print ' draw %0.0f, %0.0f, %0.0f, %0.0f' % (x0,y0,w,-h)
1002
elif cmd == 'COLBACKGROUNDS':
1003
#cycle through colours columnwise
1004
colorCycle = map(colors.toColorOrNone, arg)
1005
count = len(colorCycle)
1006
colCount = ec - sc + 1
1007
for i in range(colCount):
1008
color = colorCycle[i%count]
1009
w = self._colWidths[sc + i]
1011
canv.setFillColor(color)
1012
canv.rect(x0, y0, w, h, stroke=0,fill=1)
1014
else: #cmd=='BACKGROUND'
1015
canv.setFillColor(colors.toColor(arg))
1016
canv.rect(x0, y0, w, h, stroke=0,fill=1)
1018
def _drawCell(self, cellval, cellstyle, (colpos, rowpos), (colwidth, rowheight)):
1019
if self._curcellstyle is not cellstyle:
1020
cur = self._curcellstyle
1021
if cur is None or cellstyle.color != cur.color:
1022
self.canv.setFillColor(cellstyle.color)
1023
if cur is None or cellstyle.leading != cur.leading or cellstyle.fontname != cur.fontname or cellstyle.fontsize != cur.fontsize:
1024
self.canv.setFont(cellstyle.fontname, cellstyle.fontsize, cellstyle.leading)
1025
self._curcellstyle = cellstyle
1027
just = cellstyle.alignment
1028
valign = cellstyle.valign
1030
if n in _SeqTypes or isinstance(cellval,Flowable):
1031
if not n in _SeqTypes: cellval = (cellval,)
1032
# we assume it's a list of Flowables
1035
w, h = self._listCellGeom(cellval,colwidth,cellstyle,W=W, H=H,aH=rowheight)
1037
y = rowpos + rowheight - cellstyle.topPadding
1038
elif valign=='BOTTOM':
1039
y = rowpos+cellstyle.bottomPadding + h
1041
y = rowpos+(rowheight+cellstyle.bottomPadding-cellstyle.topPadding+h)/2.0
1042
y = y+cellval[0].getSpaceBefore()
1043
for v, w, h in map(None,cellval,W,H):
1044
if just=='LEFT': x = colpos+cellstyle.leftPadding
1045
elif just=='RIGHT': x = colpos+colwidth-cellstyle.rightPadding - w
1046
elif just in ('CENTRE', 'CENTER'):
1047
x = colpos+(colwidth+cellstyle.leftPadding-cellstyle.rightPadding-w)/2.0
1049
raise ValueError, 'Invalid justification %s' % just
1050
y = y - v.getSpaceBefore()
1052
v.drawOn(self.canv,x,y)
1053
y = y - v.getSpaceAfter()
1056
draw = self.canv.drawString
1057
x = colpos + cellstyle.leftPadding
1058
elif just in ('CENTRE', 'CENTER'):
1059
draw = self.canv.drawCentredString
1060
x = colpos + colwidth * 0.5
1061
elif just == 'RIGHT':
1062
draw = self.canv.drawRightString
1063
x = colpos + colwidth - cellstyle.rightPadding
1064
elif just == 'DECIMAL':
1065
draw = self.canv.drawAlignedString
1066
x = colpos + colwidth - cellstyle.rightPadding
1068
raise ValueError, 'Invalid justification %s' % just
1069
if n is StringType: val = cellval
1070
else: val = str(cellval)
1071
vals = string.split(val, "\n")
1073
leading = cellstyle.leading
1074
fontsize = cellstyle.fontsize
1075
if valign=='BOTTOM':
1076
y = rowpos + cellstyle.bottomPadding+n*leading-fontsize
1078
y = rowpos + rowheight - cellstyle.topPadding - fontsize
1079
elif valign=='MIDDLE':
1080
#tim roberts pointed out missing fontsize correction 2004-10-04
1081
y = rowpos + (cellstyle.bottomPadding + rowheight-cellstyle.topPadding+n*leading)/2.0 - fontsize
1083
raise ValueError, "Bad valign: '%s'" % str(valign)
1090
# drawCentredString(self, x, y, text) where x is center
1091
# drawRightString(self, x, y, text) where x is right
1092
# drawString(self, x, y, text) where x is left
1094
_LineOpMap = { 'GRID':'_drawGrid',
1096
'OUTLINE':'_drawBox',
1097
'INNERGRID':'_drawInnerGrid',
1098
'LINEBELOW':'_drawHLinesB',
1099
'LINEABOVE':'_drawHLines',
1100
'LINEBEFORE':'_drawVLines',
1101
'LINEAFTER':'_drawVLinesA', }
1103
class LongTable(Table):
1104
'''Henning von Bargen's changes will be active'''
1105
_longTableOptimize = 1
1107
LINECOMMANDS = _LineOpMap.keys()
1109
def _isLineCommand(cmd):
1110
return cmd[0] in LINECOMMANDS
1112
def _setCellStyle(cellStyles, i, j, op, values):
1113
#new = CellStyle('<%d, %d>' % (i,j), cellStyles[i][j])
1114
#cellStyles[i][j] = new
1115
## modify in place!!!
1116
new = cellStyles[i][j]
1119
new.fontname = values[0]
1121
new.fontsize = values[1]
1123
new.leading = values[2]
1125
new.leading = new.fontsize*1.2
1126
elif op in ('FONTNAME', 'FACE'):
1127
new.fontname = values[0]
1128
elif op in ('SIZE', 'FONTSIZE'):
1129
new.fontsize = values[0]
1130
elif op == 'LEADING':
1131
new.leading = values[0]
1132
elif op == 'TEXTCOLOR':
1133
new.color = colors.toColor(values[0], colors.Color(0,0,0))
1134
elif op in ('ALIGN', 'ALIGNMENT'):
1135
new.alignment = values[0]
1136
elif op == 'VALIGN':
1137
new.valign = values[0]
1138
elif op == 'LEFTPADDING':
1139
new.leftPadding = values[0]
1140
elif op == 'RIGHTPADDING':
1141
new.rightPadding = values[0]
1142
elif op == 'TOPPADDING':
1143
new.topPadding = values[0]
1144
elif op == 'BOTTOMPADDING':
1145
new.bottomPadding = values[0]
1147
GRID_STYLE = TableStyle(
1148
[('GRID', (0,0), (-1,-1), 0.25, colors.black),
1149
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
1151
BOX_STYLE = TableStyle(
1152
[('BOX', (0,0), (-1,-1), 0.50, colors.black),
1153
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
1155
LABELED_GRID_STYLE = TableStyle(
1156
[('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
1157
('BOX', (0,0), (-1,-1), 2, colors.black),
1158
('LINEBELOW', (0,0), (-1,0), 2, colors.black),
1159
('LINEAFTER', (0,0), (0,-1), 2, colors.black),
1160
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
1162
COLORED_GRID_STYLE = TableStyle(
1163
[('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
1164
('BOX', (0,0), (-1,-1), 2, colors.red),
1165
('LINEBELOW', (0,0), (-1,0), 2, colors.black),
1166
('LINEAFTER', (0,0), (0,-1), 2, colors.black),
1167
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
1169
LIST_STYLE = TableStyle(
1170
[('LINEABOVE', (0,0), (-1,0), 2, colors.green),
1171
('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black),
1172
('LINEBELOW', (0,-1), (-1,-1), 2, colors.green),
1173
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
1177
# experimental iterator which can apply a sequence
1178
# of colors e.g. Blue, None, Blue, None as you move
1182
if __name__ == '__main__':
1183
from reportlab.test.test_platypus_tables import old_tables_test