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("<", "<")
-
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("&", "&")
+ if "<" in word
and ">" not in word:
+ word =
word.replace("<", "<")
+ 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]