dabo Commit
Revision 5183
Date: 2009-04-20 12:20:06 -0700 (Mon, 20 Apr 2009)
Author: Paul
Trac: http://trac.dabodev.com/changeset/5183

Changed:
U   branches/paul_reporting/dabo/lib/reportWriter.py

Log:
This is an in-progress commit so that interested people can see the
state of what I've been working on (multi-page growable paragraphs).

It is not finished.

Removed calculatedHeight property, which is an internal detail that
never should have been exposed to users.

Reworked drawing of paragraphs to work out how much vertical space 
they'll need, and how much vertical space they have, and to defer
drawing of additional text until the next page.

Refactored some of the code to calculate band and object height.

Pedro, I like the idea of the CanGrow and CanShrink properties, BTW,
but I want to get this default behavior working correctly before
thinking about it. I think that when we implement them, the defaults
will be CanGrow:False and CanShrink:False unless Height is None, in
which case CanGrow defaults to True (backward-compatibility). Then,
we could decide to deprecate a height of None (which I'm in favor
of). 


Diff:
Modified: branches/paul_reporting/dabo/lib/reportWriter.py
===================================================================
--- branches/paul_reporting/dabo/lib/reportWriter.py    2009-04-20 18:22:03 UTC 
(rev 5182)
+++ branches/paul_reporting/dabo/lib/reportWriter.py    2009-04-20 19:20:06 UTC 
(rev 5183)
@@ -717,8 +717,6 @@
                self.AvailableProps["ColumnCount"] = toPropDict(int, 1, 
                                """Specifies the number of columns in the 
frame.""")
 
-               self.AvailableProps["calculatedHeight"] = toPropDict(float, 0, 
-                               """(to remove)""")
 
        def insertRequiredElements(self):
                """Insert any missing required elements into the frameset."""
@@ -807,19 +805,21 @@
        """
        _clearMemento = True
 
-
-       def draw(self, obj, origin, getNeededHeight=False):
+       def draw(self, obj, origin=(0,0),       availableHeight=None, 
deferred=None):
                """Draw the given object on the Canvas.
 
                The object is a dictionary containing properties, and   origin 
is the (x,y)
                tuple where the object will be drawn. 
+
+               availableHeight is the height available on the current page; 
deferred is 
+               the contents of the object partially printed on the last page 
that needs
+               to continue printing now (paragraph story). 
                """
-               neededHeight = 0
-
                ## (Can't get x,y directly from object because it may have been 
modified 
                ## by the calling program to adjust for band position, and 
draw() 
                ## doesn't know about bands.)
-
+               neededHeight = 0
+               objType = obj.__class__.__name__
                c = self.Canvas
                x,y = origin
 
@@ -828,18 +828,11 @@
                ## returns between c.saveState() and c.restoreState()!
                c.saveState()
 
+               neededHeight = 0
+
                ## These properties can apply to all objects:
                width = self.getPt(obj.getProp("width"))
        
-               try:
-                       height = obj.getProp("calculatedHeight")
-               except ValueError:
-                       height = None
-               if height is not None:
-                       height = self.getPt(height)
-               else:
-                       height = self.getPt(obj.getProp("Height"))
-       
                rotation = obj.getProp("rotation")
                hAnchor = obj.getProp("hAnchor").lower()
                vAnchor = obj.getProp("vAnchor").lower()
@@ -848,15 +841,16 @@
                        x = x - width
                elif hAnchor == "center":
                        x = x - (width / 2)
-               
-               if vAnchor == "top":
-                       y = y - height
-               elif vAnchor == "middle":
-                       y = y - (height / 2)
        
+               if objType != "Frameset":       
+                       height = self.getObjectHeight(obj)
+                       if vAnchor == "top":
+                               y = y - height
+                       elif vAnchor == "middle":
+                               y = y - (height / 2)
+       
                
                ## Do specific things based on object type:
-               objType = obj.__class__.__name__
                if objType == "Rectangle":
                        d = shapes.Drawing(width, height)
                        d.rotate(rotation)
@@ -1009,16 +1003,42 @@
        
                elif objType == "Frameset":
                        # A frame is directly related to reportlab's platypus 
Frame.
+                               
                        borderWidth = self.getPt(obj.getProp("borderWidth"))
                        borderColor = obj.getProp("borderColor")
-                       frameId = obj.getProp("frameId")
+                       columnCount = obj.getProp("columnCount")
+                       columnWidth = width/columnCount
                        padLeft = self.getPt(obj.getProp("padLeft"))
                        padRight = self.getPt(obj.getProp("padRight"))
                        padTop = self.getPt(obj.getProp("padTop"))
                        padBottom = self.getPt(obj.getProp("padBottom"))
-                       columnCount = obj.getProp("columnCount")
+                       frameId = obj.getProp("frameId")
+
+                       if deferred:
+                               story = deferred
+                               neededHeight = sum([s[1] for s in story])
+                       else:
+                               story, neededHeight = self.getStory(obj)
+                       printStory = story
+                       deferredStory = []
+                       tot_p_height = 0
+                       printStoryHeight = deferredStoryHeight = 0
+                       if neededHeight > availableHeight:
+                               printStory = []
+                               for p, p_height in story:
+                                       tot_p_height += p_height
+                                       if tot_p_height + padTop + padBottom >= 
availableHeight:
+                                               deferredStory.append((p, 
p_height))
+                                               deferredStoryHeight += p_height
+                                       else:
+                                               printStory.append((p, p_height))
+                                               printStoryHeight += p_height
+                               neededHeight = printStoryHeight + padTop + 
padBottom
        
-                       columnWidth = width/columnCount
+                       if vAnchor == "top":
+                               y = y - neededHeight
+                       elif vAnchor == "middle":
+                               y = y - (neededHeight / 2)
 
                        ## Set canvas props based on our props:
                        c.translate(x, y)
@@ -1031,79 +1051,18 @@
                        else:
                                boundary = 0
 
-                       story = []      
-                       
-                       styles_ = styles.getSampleStyleSheet()
 
-                       objects = obj["Objects"]
-                       story = []
-                       for fobject in objects:
-                               objNeededHeight = 0
-
-                               t = fobject.__class__.__name__
-                               s = styles_[fobject.getProp("style")]
-                               expr = fobject.getProp("expr", 
returnException=True)
-
-                               if isinstance(s, basestring):
-                                       expr = expr.encode(self.Encoding)
-                               else:
-                                       expr = unicode(expr)
-                               s = copy.deepcopy(s)
-
-                               if fobject.has_key("fontSize"):
-                                       s.fontSize = fobject.getProp("fontSize")
-
-                               if fobject.has_key("fontName"):
-                                       s.fontName = fobject.getProp("fontName")
-                               
-                               if fobject.has_key("leading"):
-                                       s.leading = fobject.getProp("leading")
-
-                               if fobject.has_key("spaceAfter"):
-                                       s.spaceAfter = 
fobject.getProp("spaceAfter")
-       
-                               if fobject.has_key("spaceBefore"):
-                                       s.spaceBefore = 
fobject.getProp("spaceBefore")
-
-                               if fobject.has_key("leftIndent"):
-                                       s.leftIndent = 
fobject.getProp("leftIndent")
-
-                               if fobject.has_key("firstLineIndent"):
-                                       s.firstLineIndent = 
fobject.getProp("firstLineIndent")
-
-                               if t.lower() == "paragraph":
-                                       paras = expr.split("\n")
-                                       for para in paras:
-                                               if len(para) == 0: 
-                                                       # Blank line
-                                                       p = platypus.Spacer(0, 
s.leading)
-                                               else:
-                                                       def escapePara(para):
-                                                               words = 
para.split(" ")
-                                                               for idx, word 
in enumerate(words):
-                                                                       if "&" 
in word and ";" not in word:
-                                                                               
word = word.replace("&", "&")
-                                                                       if "<" 
in word and ">" not in word:
-                                                                               
word = word.replace("<", "&lt;")
-                                                                       
words[idx] = word
-                                                               return " 
".join(words)
-                                                       para = escapePara(para)
-                                                       p = ParaClass(para, s)
-                                               story.append(p)
-                                               objNeededHeight += 
p.wrap(columnWidth-padLeft-padRight, None)[1]
-
-                               neededHeight = max(neededHeight, 
objNeededHeight) + padTop + padBottom
-
                        for columnIndex in range(columnCount):
-                               f = platypus.Frame(columnIndex*columnWidth, 0, 
columnWidth, height, leftPadding=padLeft,
+                               f = platypus.Frame(columnIndex*columnWidth, 0, 
columnWidth, neededHeight, leftPadding=padLeft,
                                                rightPadding=padRight, 
topPadding=padTop,
                                                bottomPadding=padBottom, 
id=frameId, 
                                                showBoundary=boundary)
-                               if getNeededHeight:
-                                       obj["calculatedHeight"] = "%s" % 
neededHeight
-                               else:
-                                       f.addFromList(story, c)
+                               f.addFromList([s[0] for s in printStory], c)
        
+                       deferred = deferredStory
+                       neededHeight = deferredStoryHeight
+
+
                elif objType == "Image":
                        borderWidth = self.getPt(obj.getProp("borderWidth"))
                        borderColor = obj.getProp("borderColor")
@@ -1252,9 +1211,84 @@
                ## rotating, scaling, etc. are cumulative, not absolute and we 
don't want
                ## to start with a canvas in an unknown state.)
                c.restoreState()
-               return neededHeight
+               return deferred, neededHeight
 
 
+       def getStory(self, obj):
+               width = self.getPt(obj.getProp("width"))
+               padLeft = self.getPt(obj.getProp("padLeft"))
+               padRight = self.getPt(obj.getProp("padRight"))
+               padTop = self.getPt(obj.getProp("padTop"))
+               padBottom = self.getPt(obj.getProp("padBottom"))
+               columnCount = obj.getProp("columnCount")
+               columnWidth = width/columnCount
+
+               styles_ = styles.getSampleStyleSheet()
+
+               objects = obj["Objects"]
+               story = []
+               for fobject in objects:
+                       objNeededHeight = 0
+
+                       t = fobject.__class__.__name__
+                       s = styles_[fobject.getProp("style")]
+                       expr = fobject.getProp("expr", returnException=True)
+
+                       if isinstance(s, basestring):
+                               expr = expr.encode(self.Encoding)
+                       else:
+                               expr = unicode(expr)
+                       s = copy.deepcopy(s)
+
+                       if fobject.has_key("fontSize"):
+                               s.fontSize = fobject.getProp("fontSize")
+
+                       if fobject.has_key("fontName"):
+                               s.fontName = fobject.getProp("fontName")
+                               
+                       if fobject.has_key("leading"):
+                               s.leading = fobject.getProp("leading")
+
+                       if fobject.has_key("spaceAfter"):
+                               s.spaceAfter = fobject.getProp("spaceAfter")
+       
+                       if fobject.has_key("spaceBefore"):
+                               s.spaceBefore = fobject.getProp("spaceBefore")
+
+                       if fobject.has_key("leftIndent"):
+                               s.leftIndent = fobject.getProp("leftIndent")
+
+                       if fobject.has_key("firstLineIndent"):
+                               s.firstLineIndent = 
fobject.getProp("firstLineIndent")
+
+                       if t.lower() == "paragraph":
+                               paras = expr.splitlines()
+                               for idx, para in enumerate(paras):
+                                       if len(para) == 0: 
+                                               # Blank line
+                                               p = platypus.Spacer(0, 
s.leading)
+                                       else:
+                                               def escapePara(para):
+                                                       words = para.split(" ")
+                                                       for idx, word in 
enumerate(words):
+                                                               if "&" in word 
and ";" not in word:
+                                                                       word = 
word.replace("&", "&amp;")
+                                                               if "<" in word 
and ">" not in word:
+                                                                       word = 
word.replace("<", "&lt;")
+                                                               words[idx] = 
word
+                                                       return " ".join(words)
+                                               para = escapePara(para)
+                                               p = ParaClass(para, s)
+                                       p_height = 
p.wrap(columnWidth-padLeft-padRight, None)[1]
+                                       objNeededHeight += p_height
+                                       story.append((p, p_height))
+
+                       neededHeight = objNeededHeight + padTop + padBottom
+               return story, neededHeight
+
+       def getNeededHeight(self, obj):
+               return self.getStory(obj)[1]
+
        def getColorTupleFromReportLab(self, val):
                """Given a color tuple in reportlab format (values between 0 
and 1), return
                a color tuple in 0-255 format."""
@@ -1323,6 +1357,7 @@
                the PDF file will be left open so that additional pages can be 
added 
                with another call, perhaps after creating a different report 
form.
                """
+               self._calcObjectHeights = {}
                _form = self.ReportForm
                if _form is None:
                        raise ValueError("ReportForm must be set first.")
@@ -1403,7 +1438,7 @@
                                self.Variables[varName] = vv["value"]           
        
                                        
 
-               def printBand(band, y=None, group=None):
+               def printBand(band, y=None, group=None, deferred=None):
                        """Generic function for printing any band."""
 
                        _form = self.ReportForm
@@ -1425,6 +1460,12 @@
 #                      print workingPageWidth / 72, columnWidth / 72
 #                      print columnWidth, columnCount
 
+                       pf = _form.get("pageFooter")
+                       if pf is None:
+                               pfHeight = 0
+                       else:
+                               pfHeight = self.getPt(pf.getProp("Height"))
+
                        if y is None:
                                y = pageHeaderOrigin[1]
 
@@ -1439,15 +1480,12 @@
 
                        self.ReportForm.Bands[band] = CaselessDict()
 
-                       height = bandDict.getProp("Height")
-                       if height is not None:
-                               height = self.getPt(height)
-                       else:
-                               # figure out height based on the objects in the 
band.
-                               height = self.calculateBandHeight(bandDict)
+                       fixedBandHeight = self.getBandHeight(bandDict, 
onlyFixedObjects=True)
+                       # Band height accounting also for objects that grow 
downward:
+                       bandHeight = self.getBandHeight(bandDict)
 
-                       y = y - height
                        width = pageWidth - ml - mr
+                       y -= fixedBandHeight
 
                        # Non-detail band special cases:
                        if band == "pageHeader":
@@ -1458,14 +1496,7 @@
                                x,y = 0,1
                                width, height = pageWidth-1, pageHeight-1
 
-
-                       pf = _form.get("pageFooter")
-                       if pf is None:
-                               pfHeight = 0
-                       else:
-                               pfHeight = self.getPt(pf.getProp("Height"))
-
-                       if band in ("detail", "groupHeader", "groupFooter"):
+                       if band in ("groupHeader", "groupFooter", "detail"):
                                extraHeight = 0
                                if band == "groupHeader":
                                        # Also account for the height of the 
first detail record: don't print the
@@ -1473,16 +1504,14 @@
                                        # printed as well. Actually, this 
should be reworked so that any subsequent
                                        # group header records get accounted 
for as well...
                                        b = _form["detail"]
-                                       extraHeight = b.get("Height")
-                                       if extraHeight is None:
-                                               extraHeight = 
b.AvailableProps["Height"]["default"]
-                                       else:
-                                               extraHeight = eval(extraHeight)
-                                       if extraHeight is None:
-                                               extraHeight = 
self.calculateBandHeight(b)
-                                       else:
-                                               extraHeight = 
self.getPt(extraHeight)
-                               if y < pageFooterOrigin[1] + pfHeight + 
extraHeight:
+                                       extraHeight = self.getBandHeight(b)
+
+                               check = pageFooterOrigin[1] + pfHeight + 
extraHeight
+                               if bandDict.getProp("height") is not None:
+                                       # band height is fixed, won't flow to 
next page. 
+                                       check += fixedBandHeight
+
+                               if y < check:
                                        if self._currentColumn >= columnCount-1:
                                                endPage()
                                                beginPage()
@@ -1491,41 +1520,104 @@
                                        y = pageHeaderOrigin[1]
                                        if band == "detail":
                                                y = reprintGroupHeaders(y)
-                                       y = y - height
+                                       y -= fixedBandHeight
                                
                        x = ml + (self._currentColumn * columnWidth)
                                
                        self.ReportForm.Bands[band]["x"] = x
                        self.ReportForm.Bands[band]["y"] = y
                        self.ReportForm.Bands[band]["Width"] = width
-                       self.ReportForm.Bands[band]["Height"] = height
-               
+                       self.ReportForm.Bands[band]["Height"] = bandHeight
+       
                        if self.ShowBandOutlines:
                                self.printBandOutline("%s (record %s)" % (band, 
self.RecordNumber), 
-                                               x, y, width, height)
+                                               x, y, width, bandHeight)
 
-                       if bandDict.has_key("Objects"):
-                               for obj in bandDict["Objects"]:
-                                       show = obj.get("show")
-                                       if show is not None:
-                                               try:
-                                                       ev = eval(show)
-                                               except StandardError:
-                                                       ## expression failed to 
eval: default to True (show it)
-                                                       ev = True
-                                               if not ev:
-                                                       # user's show evaluated 
to False: don't print!
-                                                       continue
+                       del_deferred_idxs = []
+                       objects = bandDict.get("Objects", [])
+                       if deferred:
+                               objects = deferred
+                       for idx, obj in enumerate(objects):
+                               if isinstance(obj, tuple):
+                                       # deferred (obj, obj_deferred)
+                                       obj, obj_deferred, neededHeight = obj
+                               else:
+                                       obj_deferred = None
+                                       neededHeight = 0
+                               show = obj.get("show")
+                               if show is not None:
+                                       try:
+                                               ev = eval(show)
+                                       except StandardError:
+                                               ## expression failed to eval: 
default to True (show it)
+                                               ev = True
+                                       if not ev:
+                                               # user's show evaluated to 
False: don't print!
+                                               continue
 
-                                       x1 = self.getPt(obj.getProp("x"))
-                                       y1 = self.getPt(obj.getProp("y"))
-                                       x1 = x + x1
+                               x1 = self.getPt(obj.getProp("x"))
+                               y1 = obj_y = self.getPt(obj.getProp("y"))
+                               x1 = x + x1
+                               if obj_deferred:
+                                       y1 = y
+                               else:
                                        y1 = y + y1
-                                       self.draw(obj, (x1, y1))
-                                               
-                       return y                
 
+                               availableHeight = y - (pageFooterOrigin[1] + 
pfHeight)
+                               obj_height = obj.getProp("height")
+                               if obj_height is not None:
+                                       obj_height = self.getPt(obj_height)
+                                       if availableHeight > obj_height:
+                                               availableHeight = obj_height
+                               if bandDict.getProp("height") is not None:
+                                       if availableHeight > obj_y:
+                                               availableHeight = obj_y
+                               #availableHeight = min(availableHeight, 
bandHeight+y)
+                               new_obj_deferred, neededHeight = self.draw(obj, 
(x1, y1),       availableHeight=availableHeight,
+                                               deferred=obj_deferred)
 
+                               #if new_obj_deferred:
+                                       #print new_obj_deferred
+                                       #1/0
+                               #if used_height == 0:
+                               #       new_obj_deferred = None
+
+                               #used_height += alreadyUsedHeight
+                               if bandDict.getProp("height") is not None:
+                                       # Band height is fixed; cancel any 
deferrals.
+                                       new_obj_deferred = None
+
+                               #if used_height >= bandHeight:
+                               #       # no more room in the band for this 
object.
+                               #       new_obj_deferred = None
+
+                               if new_obj_deferred:
+                                       if obj_deferred:
+                                               # was already deferred, and now 
deferred again. WARNING: if para longer 
+                                               # than a page, we'll recurse 
forever. FIXME.
+                                               deferred[idx] = (obj, 
new_obj_deferred, neededHeight)
+                                       else:
+                                               # new deferral.
+                                               if deferred is None:
+                                                       deferred = []
+                                               deferred.append((obj, 
new_obj_deferred, neededHeight))
+                               else:
+                                       if obj_deferred:
+                                               # need to delete the old 
deferral
+                                               del_deferred_idxs.append(idx)
+
+                       del_deferred_idxs.sort(reverse=True)
+                       for idx in del_deferred_idxs:
+                               del(deferred[idx])
+
+                       if deferred:
+                               # the deferred objs will print on the next 
page. RECURSE WARNING.
+                               printBand(band=band, y=-1, group=group, 
deferred=deferred)
+                               print "deferred"
+                       print y
+                       return y
+
+
                def beginPage():
                        # Print the static bands that appear below detail in 
z-order:
                        self._pageNumber += 1
@@ -1623,27 +1715,57 @@
                        self._canvas = None
 
 
-       def calculateBandHeight(self, bandDict):
+       def getBandHeight(self, bandDict, onlyFixedObjects=False):
+               """Return the height of the band.
+
+               If the band's Height property is None, the height will be
+               calculated based on the objects in the band.
+
+               If onlyFixedObjects is True, growable objects won't be 
+               counted.
+               """
+
+               height = bandDict.getProp("Height")
+               if height is not None:
+                       # explicitly-set height
+                       return self.getPt(height)
+
+               # dynamic height: figure out based on the objects in the band.
                maxHeight = 0
-               if bandDict.has_key("Objects"):
-                       for obj in bandDict["Objects"]:
-                               y = self.getPt(obj.getProp("y"))
+               objects = bandDict.get("objects", [])
 
-                               ht = obj.getProp("Height")
-                               if ht is None:
-                                       ht = self.calculateObjectHeight(obj)
-                               ht = self.getPt(ht)
+               for obj in objects:
+                       obj_y = self.getPt(obj.getProp("y"))
+                       obj_ht = self.getObjectHeight(obj)
 
-                               thisHeight = y + ht
-                               maxHeight = max(thisHeight, maxHeight)
+                       if obj.getProp("Height") is None:
+                               # Object height grows downward to accommodate 
(not upward like
+                               # other objects), so we need to add to the 
height beyond the area
+                               # already filled by the y pos of the object.
+                               if not onlyFixedObjects:
+                                       thisHeight = obj_ht - obj_y
+                               else:
+                                       thisHeight = obj_y
+                       else:
+                               # object height is fixed.
+                               thisHeight = obj_y + obj_ht
+
+                       maxHeight = max(thisHeight, maxHeight)
                return maxHeight
 
+
+       def getObjectHeight(self, obj):
+               """Return the height of the object, in points.
+
+               If the object's height is None and it is growable (paragraph),
+               the height will be calculated.
+               """
+               obj_ht = obj.getProp("Height")
+               if obj_ht is None:
+                       obj_ht = self.getNeededHeight(obj)
+               return self.getPt(obj_ht)
                
-       def calculateObjectHeight(self, obj):
-               neededHeight = self.draw(obj, (0,0), getNeededHeight=True)
-               return neededHeight
 
-
        def getPageSize(self):
                ## Set the Page Size:
                # get the string pageSize value from the spec file:



_______________________________________________
Post Messages to: [email protected]
Subscription Maintenance: http://leafe.com/mailman/listinfo/dabo-dev
Searchable Archives: http://leafe.com/archives/search/dabo-dev
This message: 
http://leafe.com/archives/byMID/[email protected]

Reply via email to