tables.py: support for NOSPLIT, imroved cell backgrounding
authorrgbecker
Thu, 31 May 2007 15:20:42 +0000
changeset 2812 3a861a4b4cf5
parent 2811 3062d7ba9d04
child 2813 2b9091df4470
tables.py: support for NOSPLIT, imroved cell backgrounding
reportlab/platypus/tables.py
--- a/reportlab/platypus/tables.py	Thu May 24 16:49:19 2007 +0000
+++ b/reportlab/platypus/tables.py	Thu May 31 15:20:42 2007 +0000
@@ -246,15 +246,15 @@
         if not _seqRH: rowHeights = nrows*[rowHeights]
         elif len(rowHeights) != nrows:
             raise ValueError, "%s data error - %d rows in data but %d in grid" % (self.identity(),nrows, len(rowHeights))
-        for i in range(nrows):
+        for i in xrange(nrows):
             if len(data[i]) != ncols:
                 raise ValueError, "%s not enough data points in row %d!" % (self.identity(),i)
         self._rowHeights = self._argH = rowHeights
         self._colWidths = self._argW = colWidths
         cellrows = []
-        for i in range(nrows):
+        for i in xrange(nrows):
             cellcols = []
-            for j in range(ncols):
+            for j in xrange(ncols):
                 cellcols.append(CellStyle(`(i,j)`))
             cellrows.append(cellcols)
         self._cellStyles = cellrows
@@ -262,6 +262,7 @@
         self._bkgrndcmds = []
         self._linecmds = []
         self._spanCmds = []
+        self._nosplitCmds = []
         self.repeatRows = repeatRows
         self.repeatCols = repeatCols
         self.splitByRow = splitByRow
@@ -519,6 +520,9 @@
             if None in self._argH:
                 self._calc_width(availWidth,W=W)
 
+        if self._nosplitCmds:
+            self._calcNoSplitRanges()
+
         # calculate the full table height
         self._calc_height(availHeight,availWidth,W=W)
 
@@ -535,8 +539,8 @@
         Allow a couple which we know are fixed size such as
         images and graphics."""
         if upToRow is None: upToRow = self._nrows
-        for row in range(min(self._nrows, upToRow)):
-            for col in range(self._ncols):
+        for row in xrange(min(self._nrows, upToRow)):
+            for col in xrange(self._ncols):
                 value = self._cellvalues[row][col]
                 if not self._canGetWidth(value):
                     return 1
@@ -592,12 +596,12 @@
         minimums = {}
         totalMinimum = 0
         elementWidth = self._elementWidth
-        for colNo in range(self._ncols):
+        for colNo in xrange(self._ncols):
             w = W[colNo]
             if w is None or w=='*' or _endswith(w,'%'):
                 siz = 1
                 current = final = None
-                for rowNo in range(self._nrows):
+                for rowNo in xrange(self._nrows):
                     value = self._cellvalues[rowNo][colNo]
                     style = self._cellStyles[rowNo][colNo]
                     new = elementWidth(value,style)+style.leftPadding+style.rightPadding
@@ -758,6 +762,52 @@
             if y0 > y1: y0, y1 = y1, y0
 
             if x0!=x1 or y0!=y1:
+                if x0!=x1: #column span
+                    for y in xrange(y0, y1+1):
+                        for x in xrange(x0,x1+1):
+                            csa((x,y))
+                if y0!=y1: #row span
+                    for y in xrange(y0, y1+1):
+                        for x in xrange(x0,x1+1):
+                            rsa((x,y))
+
+                for y in xrange(y0, y1+1):
+                    for x in xrange(x0,x1+1):
+                        spanRanges[x,y] = None
+                # set the main entry
+                spanRanges[x0,y0] = (x0, y0, x1, y1)
+
+    def _calcNoSplitRanges(self):
+        """
+        This creates some mappings to let the later code determine
+        if a cell is part of a "nosplit" range.
+        self._nosplitRanges 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
+        """
+        self._nosplitRanges = nosplitRanges = {}
+        for x in xrange(self._ncols):
+            for y in xrange(self._nrows):
+                nosplitRanges[x,y] = (x, y, x, y)
+        self._colNoSplitCells = []
+        self._rowNoSplitCells = []
+        csa = self._colNoSplitCells.append
+        rsa = self._rowNoSplitCells.append
+        for (cmd, start, stop) in self._nosplitCmds:
+            x0, y0 = start
+            x1, y1 = stop
+
+            #normalize
+            if x0 < 0: x0 = x0 + self._ncols
+            if x1 < 0: x1 = x1 + self._ncols
+            if y0 < 0: y0 = y0 + self._nrows
+            if y1 < 0: y1 = y1 + self._nrows
+            if x0 > x1: x0, x1 = x1, x0
+            if y0 > y1: y0, y1 = y1, y0
+
+            if x0!=x1 or y0!=y1:
                 #column span
                 if x0!=x1:
                     for y in xrange(y0, y1+1):
@@ -771,9 +821,9 @@
 
                 for y in xrange(y0, y1+1):
                     for x in xrange(x0,x1+1):
-                        spanRanges[x,y] = None
+                        nosplitRanges[x,y] = None
                 # set the main entry
-                spanRanges[x0,y0] = (x0, y0, x1, y1)
+                nosplitRanges[x0,y0] = (x0, y0, x1, y1)
 
     def _calcSpanRects(self):
         """Work out rects for tables which do row and column spanning.
@@ -830,6 +880,9 @@
             self._bkgrndcmds.append(cmd)
         elif cmd[0] == 'SPAN':
             self._spanCmds.append(cmd)
+        elif cmd[0] == 'NOSPLIT':
+            # we expect op, start, stop
+            self._nosplitCmds.append(cmd)
         elif _isLineCommand(cmd):
             # we expect op, start, stop, weight, colour, cap, dashes, join
             cmd = list(cmd)
@@ -876,8 +929,8 @@
             if ec < 0: ec = ec + self._ncols
             if sr < 0: sr = sr + self._nrows
             if er < 0: er = er + self._nrows
-            for i in range(sr, er+1):
-                for j in range(sc, ec+1):
+            for i in xrange(sr, er+1):
+                for j in xrange(sc, ec+1):
                     _setCellStyle(self._cellStyles, i, j, op, values)
 
     def _drawLines(self):
@@ -1088,6 +1141,7 @@
         R0._cr_0(n,A)
         R0._cr_0(n,self._bkgrndcmds)
         R0._cr_0(n,self._spanCmds)
+        R0._cr_0(n,self._nosplitCmds)
 
         if repeatRows:
             #R1 = slelf.__class__(data[:repeatRows]+data[n:],self._argW,
@@ -1099,6 +1153,7 @@
             R1._cr_1_1(n,repeatRows,A)
             R1._cr_1_1(n,repeatRows,self._bkgrndcmds)
             R1._cr_1_1(n,repeatRows,self._spanCmds)
+            R1._cr_1_1(n,repeatRows,self._nosplitCmds)
         else:
             #R1 = slelf.__class__(data[n:], self._argW, self._argH[n:],
             R1 = self.__class__(data[n:], self._colWidths, self._argH[n:],
@@ -1108,6 +1163,7 @@
             R1._cr_1_0(n,A)
             R1._cr_1_0(n,self._bkgrndcmds)
             R1._cr_1_0(n,self._spanCmds)
+            R1._cr_1_0(n,self._nosplitCmds)
 
         R0.hAlign = R1.hAlign = self.hAlign
         R0.vAlign = R1.vAlign = self.vAlign
@@ -1115,23 +1171,27 @@
         self.onSplit(R1)
         return [R0,R1]
 
+    def _getRowImpossible(impossible,cells,ranges):
+        for xy in cells:
+            r=ranges[xy]
+            if r!=None:
+                y1,y2=r[1],r[3]
+                if y1!=y2:
+                    ymin=min(y1,y2) #normalize
+                    ymax=max(y1,y2) #normalize
+                    y=ymin+1
+                    while 1:
+                        if y>ymax: break
+                        impossible[y]=None #split at position y is impossible because of overlapping rowspan
+                        y+=1
+    _getRowImpossible=staticmethod(_getRowImpossible)
+
     def _getFirstPossibleSplitRowPosition(self,availHeight):
+        impossible={}
         if self._spanCmds:
-            impossible={}
-            for xy in self._rowSpanCells:
-                r=self._spanRanges[xy]
-                if r!=None:
-                    y1,y2=r[1],r[3]
-                    if y1!=y2:
-                        ymin=min(y1,y2) #normalize
-                        ymax=max(y1,y2) #normalize
-                        y=ymin+1
-                        while 1:
-                            if y>ymax: break
-                            impossible[y]=None #split at position y is impossible because of overlapping rowspan
-                            y=y+1
-        else:
-            impossible={} # any split possible because table does *not* have rowspans
+            self._getRowImpossible(impossible,self._rowSpanCells,self._spanRanges)
+        if self._nosplitCmds:
+            self._getRowImpossible(impossible,self._rowNoSplitCells,self._nosplitRanges)
         h = 0
         n = 1
         split_at = 0 # from this point of view 0 is the first position where the table may *always* be splitted
@@ -1155,7 +1215,7 @@
     def draw(self):
         self._curweight = self._curcolor = self._curcellstyle = None
         self._drawBkgrnd()
-        if self._spanCmds == []:
+        if not self._spanCmds:
             # 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):
@@ -1163,8 +1223,8 @@
         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):
+            for rowNo in xrange(self._nrows):
+                for colNo in xrange(self._ncols):
                     cellRect = self._spanRects[colNo, rowNo]
                     if cellRect is not None:
                         (x, y, width, height) = cellRect
@@ -1176,19 +1236,28 @@
     def _drawBkgrnd(self):
         nrows = self._nrows
         ncols = self._ncols
+        canv = self.canv
+        colpositions = self._colpositions
+        rowpositions = self._rowpositions
+        rowHeights = self._rowHeights
+        colWidths = self._colWidths
+        spanRects = getattr(self,'_spanRects',None)
+        def bg(x0,y0,w,h,color):
+            canv.setFillColor(color)
+            canv.rect(x0, y0, w, h, stroke=0,fill=1)
+        A = [].append
         for cmd, (sc, sr), (ec, er), arg in self._bkgrndcmds:
             if sc < 0: sc = sc + ncols
             if ec < 0: ec = ec + ncols
             if sr < 0: sr = sr + nrows
             if er < 0: er = er + nrows
-            x0 = self._colpositions[sc]
-            y0 = self._rowpositions[sr]
-            x1 = self._colpositions[min(ec+1,ncols)]
-            y1 = self._rowpositions[min(er+1,nrows)]
+            x0 = colpositions[sc]
+            y0 = rowpositions[sr]
+            x1 = colpositions[min(ec+1,ncols)]
+            y1 = rowpositions[min(er+1,nrows)]
             w, h = x1-x0, y1-y0
-            canv = self.canv
             if callable(arg):
-                apply(arg,(self,canv, x0, y0, w, h))
+                A((0x7ffffff,arg,(self,canv, x0, y0, w, h)))
             elif cmd == 'ROWBACKGROUNDS':
                 #Need a list of colors to cycle through.  The arguments
                 #might be already colours, or convertible to colors, or
@@ -1197,29 +1266,38 @@
                 colorCycle = map(colors.toColorOrNone, arg)
                 count = len(colorCycle)
                 rowCount = er - sr + 1
-                for i in range(rowCount):
+                for i in xrange(rowCount):
                     color = colorCycle[i%count]
-                    h = self._rowHeights[sr + i]
+                    h = rowHeights[sr + i]
                     if color:
-                        canv.setFillColor(color)
-                        canv.rect(x0, y0, w, -h, stroke=0,fill=1)
+                        A((0,bg,(x0, y0, w, -h, color)))
                     y0 = y0 - h
-
             elif cmd == 'COLBACKGROUNDS':
                 #cycle through colours columnwise
                 colorCycle = map(colors.toColorOrNone, arg)
                 count = len(colorCycle)
                 colCount = ec - sc + 1
-                for i in range(colCount):
+                for i in xrange(colCount):
                     color = colorCycle[i%count]
-                    w = self._colWidths[sc + i]
+                    w = colWidths[sc + i]
                     if color:
-                        canv.setFillColor(color)
-                        canv.rect(x0, y0, w, h, stroke=0,fill=1)
+                        A((1,bg,(x0, y0, w, h, color)))
                     x0 = x0 +w
             else:   #cmd=='BACKGROUND'
-                canv.setFillColor(colors.toColor(arg))
-                canv.rect(x0, y0, w, h, stroke=0,fill=1)
+                color = colors.toColorOrNone(arg)
+                if color:
+                    if ec-sc or er-sr:
+                        A((2,bg,(x0, y0, w, h, color)))
+                    elif spanRects:
+                        xywh = spanRects.get((sc,sr))
+                        if xywh:
+                            #it's a single cell
+                            x0, y0, w, h = xywh
+                        A((3,bg,(x0, y0, w, h, color)))
+        A = A.__self__
+        A.sort()
+        for z,func,args in A:
+            func(*args)
 
     def _drawCell(self, cellval, cellstyle, (colpos, rowpos), (colwidth, rowheight)):
         if self._curcellstyle is not cellstyle: