rtl-support: merge trunk changes 3804-3823 rtl-support
authorrgbecker
Wed, 26 Jan 2011 14:03:05 +0000
branchrtl-support
changeset 3486 55cc3834554a
parent 3467 4f25b3a34c5f
child 3487 d1fb5978c2b6
rtl-support: merge trunk changes 3804-3823
docs/userguide/ch6_tables.py
src/reportlab/graphics/charts/barcharts.py
src/reportlab/graphics/charts/piecharts.py
src/reportlab/lib/colors.py
src/reportlab/lib/randomtext.py
src/reportlab/lib/sequencer.py
src/reportlab/lib/styles.py
src/reportlab/pdfbase/pdfdoc.py
src/reportlab/platypus/__init__.py
src/reportlab/platypus/doctemplate.py
src/reportlab/platypus/flowables.py
src/reportlab/platypus/paragraph.py
src/rl_addons/renderPM/_renderPM.c
src/rl_addons/rl_accel/_rl_accel.c
tests/test_pdfgen_general.py
tests/test_platypus_lists.py
tests/test_platypus_toc.py
--- a/docs/userguide/ch6_tables.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/docs/userguide/ch6_tables.py	Wed Jan 26 14:03:05 2011 +0000
@@ -2,7 +2,7 @@
 #see license.txt for license details
 #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/docs/userguide/ch6_tables.py
 from tools.docco.rl_doc_utils import *
-from reportlab.platypus import Image
+from reportlab.platypus import Image,ListFlowable, ListItem
 import reportlab
 
 heading1("Tables and TableStyles")
@@ -655,3 +655,43 @@
 disc("""
 This indexes the terms "comma (,)", "," and "...".
 """)
+
+heading2("""$ListFlowable(),ListItem()$""")
+disc("""
+Use these classes to make ordered and unordered lists.  Lists can be nested.
+""")
+
+disc("""
+$ListFlowable()$ will create an ordered list, which can contain any flowable.  The class has a number of parameters to change font, colour, size, style and position of list numbers, or of bullets in unordered lists.  The type of numbering can also be set to use lower or upper case letters ('A,B,C' etc.) or Roman numerals (capitals or lowercase) using the bulletType property.  To change the list to an unordered type, set bulletType='bullet'.
+""")
+
+disc("""
+Items within a $ListFlowable()$ list can be changed from their default appearance by wrapping them in a $ListItem()$ class and setting its properties.
+""")
+
+disc("""
+The following will create an ordered list, and set the third item to an unordered sublist.
+""")
+
+EmbeddedCode("""
+from reportlab.platypus import ListFlowable, ListItem
+from reportlab.lib.styles import getSampleStyleSheet
+styles = getSampleStyleSheet()
+style = styles["Normal"]
+t = ListFlowable(
+[
+Paragraph("Item no.1", style),
+ListItem(Paragraph("Item no. 2", style),bulletColor="green",value=7),
+ListFlowable(
+                [
+                Paragraph("sublist item 1", style),
+                ListItem(Paragraph('sublist item 2', style),bulletColor='red',value='square')
+                ],
+                bulletType='bullet',
+                start='square',
+                ),
+Paragraph("Item no.4", style),
+],
+bulletType='i'
+)
+             """)
\ No newline at end of file
--- a/src/reportlab/graphics/charts/barcharts.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/graphics/charts/barcharts.py	Wed Jan 26 14:03:05 2011 +0000
@@ -49,7 +49,7 @@
     "Abstract base class, unusable by itself."
 
     _attrMap = AttrMap(BASE=PlotArea,
-        useAbsolute = AttrMapValue(EitherOr((isBoolean,EitherOr((isString,isNumber)))), desc='Flag to use absolute spacing values; use string of gsb for finer control\n(g=groupSpacing,s=barSpacing,b=barWidth). ',advancedUsage=1),
+        useAbsolute = AttrMapValue(EitherOr((isBoolean,EitherOr((isString,isNumber)))), desc='Flag to use absolute spacing values; use string of gsb for finer control\n(g=groupSpacing,s=barSpacing,b=barWidth).',advancedUsage=1),
         barWidth = AttrMapValue(isNumber, desc='The width of an individual bar.'),
         groupSpacing = AttrMapValue(isNumber, desc='Width between groups of bars.'),
         barSpacing = AttrMapValue(isNumber, desc='Width between individual bars.'),
@@ -67,6 +67,15 @@
         categoryLabelBarSize = AttrMapValue(isNumber, desc='width to leave for a category label to go between categories.'),
         categoryLabelBarOrder = AttrMapValue(OneOf('first','last','auto'), desc='where any label bar should appear first/last'),
         barRecord = AttrMapValue(None, desc='callable(bar,label=labelText,value=value,**kwds) to record bar information', advancedUsage=1),
+        zIndexOverrides = AttrMapValue(isStringOrNone, desc='''None (the default ie use old z ordering scheme) or a ',' separated list of key=value (int/float) for new zIndex ordering. If used defaults are
+    background=0,
+    categoryAxis=1,
+    valueAxis=2,
+    bars=3,
+    barLabels=4,
+    categoryAxisGrid=5,
+    valueAxisGrid=6,
+    annotations=7''')
         )
 
     def makeSwatchSample(self, rowNo, x, y, width, height):
@@ -145,6 +154,7 @@
         self.bars[1].fillColor = colors.green
         self.bars[2].fillColor = colors.blue
         self.naLabel = None #NA_Label()
+        self.zIndexOverrides = None
 
 
     def demo(self):
@@ -194,17 +204,68 @@
         cA.configure(self._configureData)
         self.calcBarPositions()
         g = Group()
-        g.add(self.makeBackground())
-        cAdgl = getattr(cA,'drawGridLast',False)
-        vAdgl = getattr(vA,'drawGridLast',False)
-        if not cAdgl: cA.makeGrid(g,parent=self, dim=vA.getGridDims)
-        if not vAdgl: vA.makeGrid(g,parent=self, dim=cA.getGridDims)
-        g.add(self.makeBars())
-        g.add(cA)
-        g.add(vA)
-        if cAdgl: cA.makeGrid(g,parent=self, dim=vA.getGridDims)
-        if vAdgl: vA.makeGrid(g,parent=self, dim=cA.getGridDims)
-        for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale))
+
+        zIndex = getattr(self,'zIndexOverrides',None)
+        if not zIndex:
+            g.add(self.makeBackground())
+            cAdgl = getattr(cA,'drawGridLast',False)
+            vAdgl = getattr(vA,'drawGridLast',False)
+            if not cAdgl: cA.makeGrid(g,parent=self, dim=vA.getGridDims)
+            if not vAdgl: vA.makeGrid(g,parent=self, dim=cA.getGridDims)
+            g.add(self.makeBars())
+            g.add(cA)
+            g.add(vA)
+            if cAdgl: cA.makeGrid(g,parent=self, dim=vA.getGridDims)
+            if vAdgl: vA.makeGrid(g,parent=self, dim=cA.getGridDims)
+            for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale))
+        else:
+            Z=dict(
+                background=0,
+                categoryAxis=1,
+                valueAxis=2,
+                bars=3,
+                barLabels=4,
+                categoryAxisGrid=5,
+                valueAxisGrid=6,
+                annotations=7,
+                )
+            for z in zIndex.strip().split(','):
+                z = z.strip()
+                if not z: continue
+                try:
+                    k,v=z.split('=')
+                except:
+                    raise ValueError('Badly formatted zIndex clause %r in %r\nallowed variables are\n%s' % (z,zIndex,'\n'.join(['%s=%r'% (k,Z[k]) for k in sorted(Z.keys())])))
+                if k not in Z:
+                    raise ValueError('Unknown zIndex variable %r in %r\nallowed variables are\n%s' % (k,Z,'\n'.join(['%s=%r'% (k,Z[k]) for k in sorted(Z.keys())])))
+                try:
+                    v = eval(v,{})  #only constants allowed
+                    assert isinstance(v,(float,int))
+                except:
+                    raise ValueError('Bad zIndex value %r in clause %r of zIndex\nallowed variables are\n%s' % (v,z,zIndex,'\n'.join(['%s=%r'% (k,Z[k]) for k in sorted(Z.keys())])))
+                Z[k] = v
+            Z = [(v,k) for k,v in Z.iteritems()]
+            Z.sort()
+            b = self.makeBars()
+            bl = b.contents.pop(-1)
+            for v,k in Z:
+                if k=='background':
+                    g.add(self.makeBackground())
+                elif k=='categoryAxis':
+                    g.add(cA)
+                elif k=='categoryAxisGrid':
+                    cA.makeGrid(g,parent=self, dim=vA.getGridDims)
+                elif k=='valueAxis':
+                    g.add(vA)
+                elif k=='valueAxisGrid':
+                    vA.makeGrid(g,parent=self, dim=cA.getGridDims)
+                elif k=='bars':
+                    g.add(b)
+                elif k=='barLabels':
+                    g.add(bl)
+                elif k=='annotations':
+                    for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale))
+
         del self._configureData
         return g
 
--- a/src/reportlab/graphics/charts/piecharts.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/graphics/charts/piecharts.py	Wed Jan 26 14:03:05 2011 +0000
@@ -35,6 +35,10 @@
 
 _ANGLE2BOXANCHOR={0:'w', 45:'sw', 90:'s', 135:'se', 180:'e', 225:'ne', 270:'n', 315: 'nw', -45: 'nw'}
 _ANGLE2RBOXANCHOR={0:'e', 45:'ne', 90:'n', 135:'nw', 180:'w', 225:'sw', 270:'s', 315: 'se', -45: 'se'}
+
+_ANGLELO    = 1e-7
+_ANGLEHI    = 360.0 - _ANGLELO
+
 class WedgeLabel(Label):
     def _checkDXY(self,ba):
         pass
@@ -608,7 +612,7 @@
         a = A.append
         for i, angle in D:
             endAngle = (startAngle + (angle * whichWay))
-            if abs(angle)>=1e-5:
+            if abs(angle)>=_ANGLELO:
                 if startAngle >= endAngle:
                     aa = endAngle,startAngle
                 else:
@@ -662,6 +666,7 @@
             #all having the default style
             wedgeStyle = self.slices[i%styleCount]
             if not wedgeStyle.visible: continue
+            aa = abs(a2-a1)
 
             # is it a popout?
             cx, cy = centerx, centery
@@ -672,15 +677,15 @@
                 aveAngleRadians = averageAngle/_180_pi
                 cosAA = cos(aveAngleRadians)
                 sinAA = sin(aveAngleRadians)
-                if popout:
+                if popout and aa<_ANGLEHI:
                     # pop out the wedge
                     cx = centerx + popout*cosAA
                     cy = centery + popout*sinAA
 
-            if n > 1:
+            if aa>=_ANGLEHI:
+                theWedge = Ellipse(cx, cy, xradius, yradius)
+            else:
                 theWedge = Wedge(cx, cy, xradius, a1, a2, yradius=yradius)
-            elif n==1:
-                theWedge = Ellipse(cx, cy, xradius, yradius)
 
             theWedge.fillColor = wedgeStyle.fillColor
             theWedge.strokeColor = wedgeStyle.strokeColor
@@ -979,6 +984,7 @@
         self.lo = lo
         self.hi = hi
         self.mid = (lo+hi)*0.5
+        self.not360 = abs(hi-lo) < _ANGLEHI
 
     def __str__(self):
         return '_SL3D(%.2f,%.2f)' % (self.lo,self.hi)
@@ -995,7 +1001,7 @@
     angle_3d = 180
 
     def _popout(self,i):
-        return self.slices[i].popout or 0
+        return self._sl3d[i].not360 and self.slices[i].popout or 0
 
     def CX(self, i,d ):
         return self._cx+(d and self._xdepth_3d or 0)+self._popout(i)*cos(_2rad(self._sl3d[i].mid))
@@ -1045,8 +1051,9 @@
         _3d_angle = self.angle_3d
         _3dva = self._3dva = _360(_3d_angle+90)
         a0 = _2rad(_3dva)
-        self._xdepth_3d = cos(a0)*self.depth_3d
-        self._ydepth_3d = sin(a0)*self.depth_3d
+        depth_3d = self.depth_3d
+        self._xdepth_3d = cos(a0)*depth_3d
+        self._ydepth_3d = sin(a0)*depth_3d
         self._cx = self.x+self.width/2.0
         self._cy = self.y+(self.height - self._ydepth_3d)/2.0
         radiusx = radiusy = self._cx-self.x
@@ -1098,7 +1105,8 @@
             sl = _sl3d[i]
             lo = angle0 = sl.lo
             hi = angle1 = sl.hi
-            if abs(hi-lo)<=1e-7: continue
+            aa = abs(hi-lo)
+            if aa<_ANGLELO: continue
             fillColor = _getShaded(style.fillColor,style.fillColorShaded,style.shading)
             strokeColor = _getShaded(style.strokeColor,style.strokeColorShaded,style.shading) or fillColor
             strokeWidth = style.strokeWidth
@@ -1106,31 +1114,39 @@
             cy0 = CY(i,0)
             cx1 = CX(i,1)
             cy1 = CY(i,1)
-            #background shaded pie bottom
-            g.add(Wedge(cx1,cy1,radiusx, lo, hi,yradius=radiusy,
-                            strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,
-                            strokeLineJoin=1))
-            #connect to top
-            if lo < a0 < hi: angle0 = a0
-            if lo < a1 < hi: angle1 = a1
-            p = ArcPath(strokeColor=strokeColor, fillColor=fillColor,strokeWidth=strokeWidth,strokeLineJoin=1)
-            p.addArc(cx1,cy1,radiusx,angle0,angle1,yradius=radiusy,moveTo=1)
-            p.lineTo(OX(i,angle1,0),OY(i,angle1,0))
-            p.addArc(cx0,cy0,radiusx,angle0,angle1,yradius=radiusy,reverse=1)
-            p.closePath()
-            if angle0<=_3dva and angle1>=_3dva:
-                rd = 0
-            else:
-                rd = min(rad_dist(angle0),rad_dist(angle1))
-            S.append((rd,p))
-            _fillSide(S,i,lo,strokeColor,strokeWidth,fillColor)
-            _fillSide(S,i,hi,strokeColor,strokeWidth,fillColor)
+            if depth_3d:
+                #background shaded pie bottom
+                g.add(Wedge(cx1,cy1,radiusx, lo, hi,yradius=radiusy,
+                                strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,
+                                strokeLineJoin=1))
+                #connect to top
+                if lo < a0 < hi: angle0 = a0
+                if lo < a1 < hi: angle1 = a1
+                p = ArcPath(strokeColor=strokeColor, fillColor=fillColor,strokeWidth=strokeWidth,strokeLineJoin=1)
+                p.addArc(cx1,cy1,radiusx,angle0,angle1,yradius=radiusy,moveTo=1)
+                p.lineTo(OX(i,angle1,0),OY(i,angle1,0))
+                p.addArc(cx0,cy0,radiusx,angle0,angle1,yradius=radiusy,reverse=1)
+                p.closePath()
+                if angle0<=_3dva and angle1>=_3dva:
+                    rd = 0
+                else:
+                    rd = min(rad_dist(angle0),rad_dist(angle1))
+                S.append((rd,p))
+                _fillSide(S,i,lo,strokeColor,strokeWidth,fillColor)
+                _fillSide(S,i,hi,strokeColor,strokeWidth,fillColor)
 
             #bright shaded top
             fillColor = style.fillColor
             strokeColor = style.strokeColor or fillColor
             T.append(Wedge(cx0,cy0,radiusx,lo,hi,yradius=radiusy,
                             strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,strokeLineJoin=1))
+            if aa>=_ANGLELARGE:
+                theWedge = Ellipse(cx0, cy0, radiusx, radiusy,
+                            strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,strokeLineJoin=1)
+            else:
+                theWedge = Wedge(cx0,cy0,radiusx,lo,hi,yradius=radiusy,
+                            strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,strokeLineJoin=1)
+            T.append(theWedge)
 
             text = labels[i]
             if style.label_visible and text:
--- a/src/reportlab/lib/colors.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/lib/colors.py	Wed Jan 26 14:03:05 2011 +0000
@@ -70,6 +70,14 @@
     def hexvala(self):
         return '0x%02x%02x%02x%02x' % self.bitmap_rgba()
 
+    def int_rgb(self):
+        v = self.bitmap_rgb()
+        return v[0]<<16|v[1]<<8|v[2]
+
+    def int_rgba(self):
+        v = self.bitmap_rgba()
+        return int((v[0]<<24|v[1]<<16|v[2]<<8|v[3])&0xffffff)
+
     _cKwds='red green blue alpha'.split()
     def cKwds(self):
         for k in self._cKwds:
--- a/src/reportlab/lib/randomtext.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/lib/randomtext.py	Wed Jan 26 14:03:05 2011 +0000
@@ -346,4 +346,12 @@
     return output
 
 if __name__=='__main__':
-    print chomsky(5)
+    import sys
+    argv = sys.argv[1:]
+    if argv:
+        theme = argv.pop(0)
+        if argv:
+            sentences = int(argv.pop(0))
+        print randomText(theme,sentences)
+    else:
+        print chomsky(5)
--- a/src/reportlab/lib/sequencer.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/lib/sequencer.py	Wed Jan 26 14:03:05 2011 +0000
@@ -46,6 +46,14 @@
     n = (num -1) % 26
     return chr(n+97)
 
+_type2formatter = {
+        'I':_format_I,
+        'i':_format_i,
+        '1':_format_123,
+        'A':_format_ABC,
+        'a':_format_abc,
+        }
+
 class _Counter:
     """Private class used by Sequencer.  Each counter
     knows its format, and the IDs of anything it
--- a/src/reportlab/lib/styles.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/lib/styles.py	Wed Jan 26 14:03:05 2011 +0000
@@ -18,6 +18,7 @@
         'PropertySet',
         'ParagraphStyle',
         'LineStyle',
+        'ListStyle',
         'StyleSheet1',
         'getSampleStyleSheet',
         )
@@ -57,7 +58,9 @@
         # very strict that only keys in class defaults are
         # allowed, so they cannot inherit
         self.refresh()
+        self._setKwds(**kw)
 
+    def _setKwds(self,**kw):
         #step three - copy keywords if any
         for (key, value) in kw.items():
              self.__dict__[key] = value
@@ -85,6 +88,13 @@
             value = self.__dict__.get(key, None)
             print indent + '%s = %s' % (key, value)
 
+    def clone(self, name, parent=None, **kwds):
+        r = self.__class__(name,parent)
+        r.__dict__ = self.__dict__.copy()
+        r.parent = parent is None and self or parent
+        r._setKwds(**kwds)
+        return r
+
 class ParagraphStyle(PropertySet):
     defaults = {
         'fontName':_baseFontName,
@@ -122,6 +132,21 @@
         canvas.setLineWidth(1)
         #etc. etc.
 
+class ListStyle(PropertySet):
+    defaults = dict(
+                leftIndent=18,
+                rightIndent=0,
+                bulletAlign='left',
+                bulletType='1',
+                bulletColor='black',
+                bulletFontName='Helvetica',
+                bulletFontSize=12,
+                bulletOffsetY=0,
+                bulletDedent='auto',
+                bulletDir='ltr',
+                bulletFormat=None,
+                )
+
 _stylesheet1_undefined = object()
 
 class StyleSheet1:
--- a/src/reportlab/pdfbase/pdfdoc.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/pdfbase/pdfdoc.py	Wed Jan 26 14:03:05 2011 +0000
@@ -2008,7 +2008,7 @@
             self.Annots = None
         else:
             #these must be transferred to the page when the form is used
-            raise ValueError, "annotations not reimplemented yet"
+            raise ValueError("annotations don't work in PDFFormXObjects yet")
         if not self.Contents:
             stream = self.stream
             if not stream:
--- a/src/reportlab/platypus/__init__.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/platypus/__init__.py	Wed Jan 26 14:03:05 2011 +0000
@@ -6,7 +6,7 @@
 
 from reportlab.platypus.flowables import Flowable, Image, Macro, PageBreak, Preformatted, Spacer, XBox, \
                         CondPageBreak, KeepTogether, TraceInfo, FailOnWrap, FailOnDraw, PTOContainer, \
-                        KeepInFrame, ParagraphAndImage, ImageAndFlowables
+                        KeepInFrame, ParagraphAndImage, ImageAndFlowables, ListFlowable, ListItem
 from reportlab.platypus.paragraph import Paragraph, cleanBlockQuotedText, ParaLines
 from reportlab.platypus.paraparser import ParaFrag
 from reportlab.platypus.tables import Table, TableStyle, CellStyle, LongTable
--- a/src/reportlab/platypus/doctemplate.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/platypus/doctemplate.py	Wed Jan 26 14:03:05 2011 +0000
@@ -773,9 +773,9 @@
                 else:
                     n = 0
                 if n:
-                    if not isinstance(S[0],(PageBreak,SlowPageBreak,ActionFlowable)):
+                    if not isinstance(S[0],(PageBreak,SlowPageBreak,ActionFlowable,LIIndenter)):
                         if not frame.add(S[0], canv, trySplit=0):
-                            ident = "Splitting error(n==%d) on page %d in\n%s" % (n,self.page,self._fIdent(f,60,frame))
+                            ident = "Splitting error(n==%d) on page %d in\n%s\nS[0]=%s" % (n,self.page,self._fIdent(f,60,frame),self._fIdent(S[0],60,frame))
                             #leave to keep apart from the raise
                             raise LayoutError(ident)
                         self._curPageFlowableCount += 1
--- a/src/reportlab/platypus/flowables.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/platypus/flowables.py	Wed Jan 26 14:03:05 2011 +0000
@@ -37,6 +37,7 @@
 __all__=('TraceInfo','Flowable','XBox','Preformatted','Image','Spacer','PageBreak','SlowPageBreak',
         'CondPageBreak','KeepTogether','Macro','CallerMacro','ParagraphAndImage',
         'FailOnWrap','HRFlowable','PTOContainer','KeepInFrame','UseUpSpace',
+        'ListFlowable','ListItem','LIIndenter',
         'DocAssign', 'DocExec', 'DocAssert', 'DocPara', 'DocIf', 'DocWhile',
         )
 class TraceInfo:
@@ -1222,6 +1223,384 @@
         frame.add_generated_content(*G)
         return 0,0
 
+
+from reportlab.lib.sequencer import _type2formatter
+_bulletNames = dict(
+                circle=u'\u25cf',
+                square=u'\u25a0',
+                disc=u'\u25cf',
+                diamond=u'\u25c6',
+                rarrowhead=u'\u27a4',
+                )
+
+def _bulletFormat(value,type='1',format=None):
+    if type=='bullet':
+        s = _bulletNames.get(value,value)
+    else:
+        s = _type2formatter[type](int(value))
+
+    if format:
+        if isinstance(format,basestring):
+            s = format % s
+        elif callable(format):
+            s = format(s)
+        else:
+            raise ValueError('unexpected BulletDrawer format %r' % format)
+    return s
+
+class BulletDrawer:
+    def __init__(self,
+                    value='0',
+                    bulletAlign='left',
+                    bulletType='1',
+                    bulletColor='black',
+                    bulletFontName='Helvetica',
+                    bulletFontSize=12,
+                    bulletOffsetY=0,
+                    bulletDedent=0,
+                    bulletDir='ltr',
+                    bulletFormat=None,
+                    ):
+        self.value = value
+        self._bulletAlign = bulletAlign
+        self._bulletType = bulletType
+        self._bulletColor = bulletColor
+        self._bulletFontName = bulletFontName
+        self._bulletFontSize = bulletFontSize
+        self._bulletOffsetY = bulletOffsetY
+        self._bulletDedent = bulletDedent
+        self._bulletDir = bulletDir
+        self._bulletFormat = bulletFormat
+
+    def drawOn(self,indenter,canv,x,y,_sW=0):
+        value = self.value
+        if not value: return
+        canv.saveState()
+        canv.translate(x, y)
+
+        y = indenter.height-self._bulletFontSize+self._bulletOffsetY
+        if self._bulletDir=='rtl':
+            x = indenter.width - indenter._rightIndent + self._bulletDedent
+        else:
+            x = indenter._leftIndent - self._bulletDedent
+        canv.setFont(self._bulletFontName,self._bulletFontSize)
+        canv.setFillColor(self._bulletColor)
+        bulletAlign = self._bulletAlign
+        value = _bulletFormat(value,self._bulletType,self._bulletFormat)
+
+        if bulletAlign=='left':
+            canv.drawString(x,y,value)
+        elif bulletAlign=='right':
+            canv.drawRightString(x,y,value)
+        elif bulletAlign in ('center','centre'):
+            canv.drawCentredString(x,y,value)
+        elif bulletAlign.startswith('numeric') or bulletAlign.startswith('decimal'):
+            pc = bulletAlign[7:].strip() or '.'
+            canv.drawAlignedString(x,y,value,pc)
+        else:
+            raise ValueError('Invalid bulletAlign: %r' % bulletAlign)
+        canv.restoreState()
+
+def _computeBulletWidth(b,value):
+    value = _bulletFormat(value,b._bulletType,b._bulletFormat)
+    return stringWidth(value,b._bulletFontName,b._bulletFontSize)
+
+class LIIndenter(Flowable):
+    _LIIndenterAttrs = '_flowable _bullet _leftIndent _rightIndent width height spaceBefore spaceAfter'.split()
+    def __init__(self,flowable,leftIndent=0,rightIndent=0,bullet=None, spaceBefore=None, spaceAfter=None):
+        self._flowable = flowable
+        self._bullet = bullet
+        self._leftIndent = leftIndent
+        self._rightIndent = rightIndent
+        self.width = None
+        self.height = None
+        if spaceBefore is not None:
+            self.spaceBefore = spaceBefore
+        if spaceAfter is not None:
+            self.spaceAfter = spaceAfter
+
+    def wrap(self, aW, aH):
+        w,h = self._flowable.wrap(aW-self._leftIndent-self._rightIndent, aH)
+        self.width = w+self._leftIndent+self._rightIndent
+        self.height = h
+        return self.width,h
+
+    def split(self, aW, aH):
+        S = self._flowable.split(aW-self._leftIndent-self._rightIndent, aH)
+        return [
+                LIIndenter(s,
+                        leftIndent=self._leftIndent,
+                        rightIndent=self._rightIndent,
+                        bullet = (s is S[0] and self._bullet or None),
+                        ) for s in S
+                ]
+
+    def drawOn(self, canv, x, y, _sW=0):
+        if self._bullet:
+            self._bullet.drawOn(self,canv,x,y,0)
+        self._flowable.drawOn(canv,x+self._leftIndent,y,max(0,_sW-self._leftIndent-self._rightIndent))
+
+    def __getattr__(self,a):
+        if a in self._LIIndenterAttrs:
+            try:
+                return self.__dict__[a]
+            except KeyError:
+                if a not in ('spaceBefore','spaceAfter'):
+                    raise
+        return getattr(self._flowable,a)
+
+    def __setattr__(self,a,v):
+        if a in self._LIIndenterAttrs:
+            self.__dict__[a] = v
+        else:
+            setattr(self._flowable,a,v)
+
+    def __delattr__(self,a):
+        if a in self._LIIndenterAttrs:
+            del self.__dict__[a]
+        else:
+            delattr(self._flowable,a)
+
+from reportlab.lib.styles import ListStyle
+class ListItem:
+    def __init__(self,
+                    flowables,  #the initial flowables
+                    style=None,
+                    #leftIndent=18,
+                    #rightIndent=0,
+                    #spaceBefore=None,
+                    #spaceAfter=None,
+                    #bulletType='1',
+                    #bulletColor='black',
+                    #bulletFontName='Helvetica',
+                    #bulletFontSize=12,
+                    #bulletOffsetY=0,
+                    #bulletDedent='auto',
+                    #bulletDir='ltr',
+                    #bulletFormat=None,
+                    **kwds
+                    ):
+        if not isinstance(flowables,(list,tuple)):
+            flowables = (flowables,)
+        self._flowables = flowables
+        params = self._params = {}
+
+        if style:
+            if not isinstance(style,ListStyle):
+                raise ValueError('%s style argument not a ListStyle' % self.__class__.__name__)
+            self._style = style
+
+        for k in ListStyle.defaults:
+            if k in kwds:
+                v = kwds.get(k)
+            elif style:
+                v = getattr(style,k)
+            else:
+                continue
+            params[k] = v
+
+        for k in ('value', 'spaceBefore','spaceAfter'):
+            v = kwds.get(k,getattr(style,k,None))
+            if v is not None:
+                params[k] = v
+
+class _LIParams:
+    def __init__(self,flowable,params,value,first):
+        self.flowable = flowable
+        self.params = params
+        self.value = value
+        self.first= first
+
+class ListFlowable(Flowable):
+    def __init__(self,
+                    flowables,  #the initial flowables
+                    start=1,
+                    style=None,
+                    #leftIndent=18,
+                    #rightIndent=0,
+                    #spaceBefore=None,
+                    #spaceAfter=None,
+                    #bulletType='1',
+                    #bulletColor='black',
+                    #bulletFontName='Helvetica',
+                    #bulletFontSize=12,
+                    #bulletOffsetY=0,
+                    #bulletDedent='auto',
+                    #bulletDir='ltr',
+                    #bulletFormat=None,
+                    **kwds
+                    ):
+        self._start = start
+        self._flowables = flowables
+
+        if style:
+            if not isinstance(style,ListStyle):
+                raise ValueError('%s style argument not a ListStyle' % self.__class__.__name__)
+            self.style = style
+
+        for k,v in ListStyle.defaults.iteritems():
+            setattr(self,'_'+k,kwds.get(k,getattr(style,k,v)))
+
+        for k in ('spaceBefore','spaceAfter'):
+            v = kwds.get(k,getattr(style,k,None))
+            if v is not None:
+                setattr(self,k,v)
+
+    def wrap(self,aW,aH):
+        return aW,0x7fffffff    #force a split
+
+    def _flowablesIter(self):
+        for f in self._flowables:
+            if isinstance(f,(list,tuple)):
+                if f:
+                    for i, z in enumerate(f):
+                        yield i==0 and not isinstance(z,LIIndenter), z
+            elif isinstance(f,ListItem):
+                params = f._params
+                if not params:
+                    #meerkat simples just a list like object
+                    for i, z in enumerate(f._flowables):
+                        if isinstance(z,LIIndenter):
+                            raise ValueError('LIIndenter not allowed in ListItem')
+                        yield i==0, z
+                else:
+                    params = params.copy()
+                    value = params.pop('value',None)
+                    spaceBefore = params.pop('spaceBefore',None)
+                    spaceAfter = params.pop('spaceAfter',None)
+                    n = len(f._flowables) - 1
+                    for i, z in enumerate(f._flowables):
+                        P = params.copy()
+                        if not i and spaceBefore is not None:
+                            P['spaceBefore'] = spaceBefore
+                        if i==n and spaceAfter is not None:
+                            P['spaceAfter'] = spaceAfter
+                        if i: value=None
+                        yield 0, _LIParams(z,P,value,i==0)
+            else:
+                yield not isinstance(f,LIIndenter), f
+
+    def _makeLIIndenter(self,flowable, bullet, params=None):
+        if params:
+            leftIndent = params.get('leftIndent',self._leftIndent)
+            rightIndent = params.get('rightIndent',self._rightIndent)
+            spaceBefore = params.get('spaceBefore',None)
+            spaceAfter = params.get('spaceAfter',None)
+            return LIIndenter(flowable,leftIndent,rightIndent,bullet,spaceBefore=spaceBefore,spaceAfter=spaceAfter)
+        else:
+            return LIIndenter(flowable,self._leftIndent,self._rightIndent,bullet)
+
+    def _makeBullet(self,value,params=None):
+        if params is None:
+            def getp(a):
+                return getattr(self,'_'+a)
+        else:
+            style = getattr(params,'style',None)
+            def getp(a):
+                if a in params: return params[a]
+                if style and a in style.__dict__: return getattr(self,a)
+                return getattr(self,'_'+a)
+
+        return BulletDrawer(
+                    value=value,
+                    bulletAlign=getp('bulletAlign'),
+                    bulletType=getp('bulletType'),
+                    bulletColor=getp('bulletColor'),
+                    bulletFontName=getp('bulletFontName'),
+                    bulletFontSize=getp('bulletFontSize'),
+                    bulletOffsetY=getp('bulletOffsetY'),
+                    bulletDedent=getp('calcBulletDedent'),
+                    bulletDir=getp('bulletDir'),
+                    bulletFormat=getp('bulletFormat'),
+                    )
+
+    def split(self,aW,aH):
+        value = self._start
+        bt = self._bulletType
+        inc = int(bt in '1aAiI')
+        if inc: value = int(value)
+
+        bd = self._bulletDedent
+        if bd=='auto':
+            align = self._bulletAlign
+            dir = self._bulletDir
+            if dir=='ltr' and align=='left':
+                bd = self._leftIndent
+            elif align=='right':
+                bd = self._rightIndent
+            else:
+                #we need to work out the maximum width of any of the labels
+                tvalue = value
+                maxW = 0
+                for d,f in self._flowablesIter():
+                    if d:
+                        maxW = max(maxW,_computeBulletWidth(self,tvalue))
+                        if inc: tvalue += inc
+                    elif isinstance(f,LIIndenter):
+                        b = f._bullet
+                        if b:
+                            if b.bulletType==bt:
+                                maxW = max(maxW,_computeBulletWidth(b,b.value))
+                                tvalue = int(b.value)
+                        else:
+                            maxW = max(maxW,_computeBulletWidth(self,tvalue))
+                        if inc: tvalue += inc
+                if dir=='ltr':
+                    if align=='right':
+                        bd = self._leftIndent - maxW
+                    else:
+                        bd = self._leftIndent - maxW*0.5
+                elif align=='left':
+                    bd = self._rightIndent - maxW
+                else:
+                    bd = self._rightIndent - maxW*0.5
+
+        self._calcBulletDedent = bd
+
+        S = []
+        aS = S.append
+        i=0
+        for d,f in self._flowablesIter():
+            fparams = {}
+            if not i:
+                i += 1
+                spaceBefore = getattr(self,'spaceBefore',None)
+                if spaceBefore is not None:
+                    fparams['spaceBefore'] = spaceBefore
+            if d:
+                aS(self._makeLIIndenter(f,bullet=self._makeBullet(value),params=fparams))
+                if inc: value += inc
+            elif isinstance(f,LIIndenter):
+                b = f._bullet
+                if b:
+                    if b.bulletType!=bt:
+                        raise ValueError('Included LIIndenter bulletType=%s != OrderedList bulletType=%s' % (b.bulletType,bt))
+                    value = int(b.value)
+                else:
+                    f._bullet = self._makeBullet(value,params=getattr(f,'params',None))
+                if fparams:
+                    f.__dict__['spaceBefore'] = max(f.__dict__.get('spaceBefore',0),spaceBefore)
+                aS(f)
+                if inc: value += inc
+            elif isinstance(f,_LIParams):
+                fparams.update(f.params)
+                z = self._makeLIIndenter(f.flowable,bullet=None,params=fparams)
+                if f.first:
+                    if f.value is not None:
+                        value = f.value
+                        if inc: value = int(value)
+                    z._bullet = self._makeBullet(value,f.params)
+                    if inc: value += inc
+                aS(z)
+            else:
+                aS(self._makeLIIndenter(f,bullet=None,params=fparams))
+
+        spaceAfter = getattr(self,'spaceAfter',None)
+        if spaceAfter is not None:
+            f=S[-1]
+            f.__dict__['spaceAfter'] = max(f.__dict__.get('spaceAfter',0),spaceAfter)
+        return S
+
 class DocAssign(NullDraw):
     '''At wrap time this flowable evaluates var=expr in the doctemplate namespace'''
     _ZEROSIZE=1
--- a/src/reportlab/platypus/paragraph.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/reportlab/platypus/paragraph.py	Wed Jan 26 14:03:05 2011 +0000
@@ -299,6 +299,18 @@
                     xs.link = f.link
                     xs.link_x = cur_x_s
                     xs.linkColor = xs.textColor
+            bg = getattr(f,'backColor',None)
+            if bg and not xs.backColor:
+                xs.backColor = bg
+                xs.backColor_x = cur_x_s
+            elif xs.backColor:
+                if not bg:
+                    xs.backColors.append( (xs.backColor_x, cur_x_s, xs.backColor) )
+                    xs.backColor = None
+                elif f.backColor!=xs.backColor or xs.textColor!=xs.backColor:
+                    xs.backColors.append( (xs.backColor_x, cur_x_s, xs.backColor) )
+                    xs.backColor = bg
+                    xs.backColor_x = cur_x_s
             txtlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize)
             cur_x += txtlen
             nSpaces += text.count(' ')+_nbspCount(text)
@@ -309,6 +321,8 @@
         xs.strikes.append( (xs.strike_x, cur_x_s, xs.strikeColor) )
     if xs.link:
         xs.links.append( (xs.link_x, cur_x_s, xs.link,xs.linkColor) )
+    if xs.backColor:
+        xs.backColors.append( (xs.backColor_x, cur_x_s, xs.backColor) )
     if tx._x0!=x0:
         setXPos(tx,x0-tx._x0)
 
@@ -355,7 +369,7 @@
             'returns 1 if two ParaFrags map out the same'
             if (hasattr(f,'cbDefn') or hasattr(g,'cbDefn')
                     or hasattr(f,'lineBreak') or hasattr(g,'lineBreak')): return 0
-            for a in ('fontName', 'fontSize', 'textColor', 'rise', 'underline', 'strike', 'link'):
+            for a in ('fontName', 'fontSize', 'textColor', 'rise', 'underline', 'strike', 'link', "backColor"):
                 if getattr(f,a,None)!=getattr(g,a,None): return 0
             return 1
 
@@ -625,6 +639,13 @@
     xs.links = []
     xs.link=None
     xs.linkColor=None
+
+    for x1,x2,c in xs.backColors:
+        tx._canvas.setFillColor(c)
+        tx._canvas.rect(x1,y,x2-x1,yl-y,stroke=0,fill=1)
+
+    xs.backColors=[]
+    xs.backColor=None
     xs.cur_y -= leading
 
 def textTransformFrags(frags,style):
@@ -1440,6 +1461,7 @@
                 tx = self.beginText(cur_x, cur_y)
                 xs = tx.XtraState=ABag()
                 xs.textColor=None
+                xs.backColor=None
                 xs.rise=0
                 xs.underline=0
                 xs.underlines=[]
@@ -1447,6 +1469,7 @@
                 xs.strike=0
                 xs.strikes=[]
                 xs.strikeColor=None
+                xs.backColors=[]
                 xs.links=[]
                 xs.link=None
                 xs.leading = style.leading
--- a/src/rl_addons/renderPM/_renderPM.c	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/rl_addons/renderPM/_renderPM.c	Wed Jan 26 14:03:05 2011 +0000
@@ -13,7 +13,7 @@
 #endif
 
 
-#define VERSION "1.07"
+#define VERSION "1.08"
 #define MODULE "_renderPM"
 static PyObject *moduleError;
 static PyObject *_version;
@@ -77,8 +77,8 @@
 		first = c[i++];
 		if(first<0x80) PyList_Append(r, PyInt_FromLong(first));
 		else if(first<0xc0){
-E0:			msg = "Imvalid UTF-8 String";
-			goto ERR;
+E0:			msg = "Invalid UTF-8 String";
+			goto RL_ERROR_EXIT;
 			}
 		else if(first<0xE0){
 			second = c[i++];
@@ -93,11 +93,11 @@
 			}
 		else{
 			msg = "UTF-8 characters outside 16-bit range not supported";
-			goto ERR;
+			goto RL_ERROR_EXIT;
 			}
 		}
 	return r;
-ERR:
+RL_ERROR_EXIT:
     Py_DECREF(r);
 	PyErr_SetString(PyExc_ValueError,msg);
     Py_INCREF(Py_None);
--- a/src/rl_addons/rl_accel/_rl_accel.c	Thu Oct 21 10:34:13 2010 +0000
+++ b/src/rl_addons/rl_accel/_rl_accel.c	Wed Jan 26 14:03:05 2011 +0000
@@ -29,7 +29,7 @@
 #ifndef min
 #	define min(a,b) ((a)<(b)?(a):(b))
 #endif
-#define VERSION "0.63"
+#define VERSION "0.64"
 #define MODULE "_rl_accel"
 
 static PyObject *moduleVersion;
@@ -332,7 +332,7 @@
 static PyObject *_sameFrag(PyObject *self, PyObject* args)
 {
 	PyObject *f, *g;
-	static char *names[] = {"fontName", "fontSize", "textColor", "rise", "underline", "strike", "link", NULL};
+	static char *names[] = {"fontName", "fontSize", "textColor", "rise", "underline", "strike", "link", "backColor", NULL};
 	int	r=0, t;
 	char **p;
 	if (!PyArg_ParseTuple(args, "OO:_sameFrag", &f, &g)) return NULL;
--- a/tests/test_pdfgen_general.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/tests/test_pdfgen_general.py	Wed Jan 26 14:03:05 2011 +0000
@@ -1038,6 +1038,13 @@
         canv.drawCentredString(0.5*S[0],0.5*S[1],'Center =(%s,%s) Page Size=%s x %s' % (0.5*S[0],0.5*S[1],S[0],S[1]))
         canv.drawRightString(S[0],2,'Bottom Right=(%s,%s) Page Size=%s x %s' % (S[0],0,S[0],S[1]))
         canv.showPage()
+        S = A4[1],A4[0]
+        canv.setPageSize(S)
+        canv.setPageRotation(0)
+        canv.drawString(0,S[1]-30,'Top Left=(%s,%s) Page Size=%s x %s' % (0,S[1],S[0],S[1]))
+        canv.drawCentredString(0.5*S[0],0.5*S[1],'Center =(%s,%s) Page Size=%s x %s' % (0.5*S[0],0.5*S[1],S[0],S[1]))
+        canv.drawRightString(S[0],32,'Bottom Right=(%s,%s) Page Size=%s x %s' % (S[0],0,S[0],S[1]))
+        canv.showPage()
         canv.save()
 
 def trySomeColors(C,enforceColorSpace=None):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_platypus_lists.py	Wed Jan 26 14:03:05 2011 +0000
@@ -0,0 +1,106 @@
+from reportlab.lib.testutils import setOutDir,makeSuiteForClasses, outputfile, printLocation
+setOutDir(__name__)
+import os,unittest
+from reportlab.platypus import Spacer, SimpleDocTemplate, Table, TableStyle, ListFlowable, ListItem, Paragraph
+from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
+from reportlab.lib.units import inch, cm
+from reportlab.lib.utils import simpleSplit
+from reportlab.lib import colors
+
+TEXTS=[
+'''We have already seen that the notion of level of grammaticalness is,
+apparently, determined by a corpus of utterance tokens upon which
+conformity has been defined by the paired utterance test.  If the
+position of the trace in (99c) were only relatively inaccessible to
+movement, a descriptively adequate grammar suffices to account for the
+traditional practice of grammarians.  Notice, incidentally, that this
+analysis of a formative as a pair of sets of features cannot be
+arbitrary in the strong generative capacity of the theory.''',
+'''
+Of course, the systematic use of complex symbols raises serious doubts
+about a stipulation to place the constructions into these various
+categories.  By combining adjunctions and certain deformations, the
+natural general principle that will subsume this case is to be regarded
+as a descriptive fact.  This suggests that this analysis of a formative
+as a pair of sets of features suffices to account for the requirement
+that branching is not tolerated within the dominance scope of a complex
+symbol.''',
+'''In the discussion of resumptive pronouns following (81), this
+selectionally introduced contextual feature is to be regarded as a
+parasitic gap construction.  With this clarification, the systematic use
+of complex symbols is not to be considered in determining a descriptive
+fact.  On our assumptions, the notion of level of grammaticalness is
+necessary to impose an interpretation on the strong generative capacity
+of the theory.  It appears that a descriptively adequate grammar is not
+subject to the requirement that branching is not tolerated within the
+dominance scope of a complex symbol.  Comparing these examples with
+their parasitic gap counterparts in (96) and (97), we see that this
+selectionally introduced contextual feature is rather different from a
+parasitic gap construction.''',
+'''
+Blah blah blah blah blah blah discipline?... naked? ... With a melon!? blah blah blah blah blah Very silly indeed Mr. Nesbitt has learned the first lesson of 'Not Being Seen', not to stand up. blah blah blah Would you like a twist of lemming sir?. 
+''',
+'''
+Blah blah blah multidisciplinary blah blah blah blah blah blah blah blah blah blah blah. Blah blah blah conceptualize blah contribution blah blah blah blah blah blah blah blah blah blah blah blah proactive. Blah blah blah blah blah blah proactive blah mastery learning blah blah blah blah blah projection Total Quality Management blah. 
+''',
+'''
+Blah Archer IV blah blah blah blah blah blah blah asteroid field USS Enterprise quantum flux blah blah Pacifica blah blah blah blah blah asteroid field. Blah blah K'Vort Class Bird-of-Prey battle bridge blah blah blah Bolian blah blah Dr. Pulaski blah blah blah blah. 
+''',
+'''
+Blah blah blah Rexx blah RFC822-compliant blah blah ...went into "yo-yo mode" blah blah blah blah blah blah security blah DOS Unix blah blah blah. Blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah Virtual Reality Modeling Language blah blah blah blah blah. 
+''',
+]
+
+class ListsTestCase(unittest.TestCase):
+    "Make documents with tables"
+
+    def test1(self):
+        styleSheet = getSampleStyleSheet()
+        doc = SimpleDocTemplate(outputfile('test_platypus_lists1.pdf'))
+        story=[]
+        sty = [ ('GRID',(0,0),(-1,-1),1,colors.green),
+            ('BOX',(0,0),(-1,-1),2,colors.red),
+            ]
+        normal = styleSheet['BodyText']
+        lpSty = normal.clone('lpSty',spaceAfter=18)
+        data = [[str(i+1), Paragraph("xx "* (i%10), styleSheet["BodyText"]), Paragraph(("blah "*(i%40)), normal)] for i in xrange(5)]
+        data1 = [[str(i+1), Paragraph(["zz ","yy "][i]*(i+3), styleSheet["BodyText"]), Paragraph(("duh  "*(i+3)), normal)] for i in xrange(2)]
+        OL = ListFlowable(
+            [
+            Paragraph("A table with 5 rows", lpSty),
+            Table(data, style=sty, colWidths = [50,100,200]),
+            ListItem(
+                Paragraph("A sublist", normal),
+                value=7,
+                ),
+            ListFlowable(
+                    [
+                    Paragraph("Another table with 3 rows", normal),
+                    Table(data[:3], style=sty, colWidths = [60,90,180]),
+                    Paragraph(TEXTS[0], normal),
+                    ],
+                    bulletType='i',
+                    ),
+            Paragraph("An unordered sublist", normal),
+            ListFlowable(
+                    [
+                    Paragraph("A table with 2 rows", normal),
+                    ListItem(Table(data1, style=sty, colWidths = [60,90,180]),bulletColor='green'),
+                    ListItem(Paragraph(TEXTS[2], normal),bulletColor='red',value='square')
+                    ],
+                    bulletType='bullet',
+                    start='circle',
+                    ),
+            Paragraph(TEXTS[1], normal),
+            ])
+
+        story.append(OL)
+        doc.build(story)
+
+def makeSuite():
+    return makeSuiteForClasses(ListsTestCase)
+
+#noruntests
+if __name__ == "__main__":
+    unittest.TextTestRunner().run(makeSuite())
+    printLocation()
--- a/tests/test_platypus_toc.py	Thu Oct 21 10:34:13 2010 +0000
+++ b/tests/test_platypus_toc.py	Wed Jan 26 14:03:05 2011 +0000
@@ -17,9 +17,10 @@
 from reportlab.lib.units import inch, cm
 from reportlab.lib.pagesizes import A4
 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
-from reportlab.platypus.paragraph import Paragraph
 from reportlab.platypus.xpreformatted import XPreformatted
 from reportlab.platypus.frames import Frame
+from reportlab.platypus.paragraph import Paragraph
+from reportlab.platypus.flowables import Flowable, Spacer
 from reportlab.platypus.doctemplate \
      import PageTemplate, BaseDocTemplate
 from reportlab.platypus import tableofcontents, PageBreak
@@ -275,7 +276,72 @@
         # I can't get one pass yet'
         #self.assertEquals(passes, 1)
 
+    def test2(self):
+        chapters = 20   #so we know we use only one page
+        from reportlab.lib.colors import pink
 
+        #TOC and this HParagraph class just handle the collection
+        TOC = []
+        fontSize = 14
+        leading = fontSize*1.2
+        descent = 0.2*fontSize
+        x = 2.5*cm          #these come from the frame size
+        y = (25+2.5)*cm - leading
+        x1 = (15+2.5)*cm
+
+        class HParagraph(Paragraph):
+            def __init__(self,key,text,*args,**kwds):
+                self._label = text
+                self._key = key
+                Paragraph.__init__(self,text,*args,**kwds)
+
+            def draw(self):
+                Paragraph.draw(self)
+                TOC.append((self._label,self.canv.getPageNumber(),self._key))
+                self.canv.bookmarkHorizontal('TOC_%s' % self._key,0,+20)
+
+        class UseForm(Flowable):
+            _ZEROSIZE = 1
+            def __init__(self,formName):
+                self._formName = formName
+                self.width = self.height = 0
+
+            def draw(self):
+                self.canv.doForm(self._formName)
+                for i in xrange(chapters):
+                    yb = y - i*leading      #baseline
+                    self.canv.linkRect('','TOC_%s' % i,(x,yb-descent,x1,yb+fontSize),thickness=0.5,color=pink,relative=0)
+
+            def drawOn(self,canvas,x,y,_sW=0):
+                Flowable.drawOn(self,canvas,0,0,canvas._pagesize[0])
+
+        class MakeForm(UseForm):
+            def draw(self):
+                canv = self.canv
+                canv.saveState()
+                canv.beginForm(self._formName)
+                canv.setFont("Helvetica",fontSize)
+                for i,(text,pageNumber,key) in enumerate(TOC):
+                    yb = y - i*leading      #baseline
+                    canv.drawString(x,yb,text)
+                    canv.drawRightString(x1,y-i*leading,str(pageNumber))
+                canv.endForm()
+                canv.restoreState()
+
+        headerStyle = makeHeaderStyle(0)
+        S = [Spacer(0,180),UseForm('TOC')]
+        RT = 'STARTUP COMPUTERS BLAH BUZZWORD STARTREK PRINTING PYTHON CHOMSKY'.split()
+        for i in range(chapters):
+            S.append(PageBreak())
+            S.append(HParagraph(i,'This is chapter %d' % (i+1), headerStyle))
+            txt = randomtext.randomText(RT[i*13 % len(RT)], 15)
+            para = Paragraph(txt, makeBodyStyle())
+            S.append(para)
+
+        S.append(MakeForm('TOC'))
+
+        doc = MyDocTemplate(outputfile('test_platypus_toc_simple.pdf'))
+        doc.build(S)
 
 def makeSuite():
     return makeSuiteForClasses(TocTestCase)