[reportlab-users] Relative indents, nested lists and reST

Sidnei da Silva reportlab-users@reportlab.com
Fri, 2 May 2003 10:37:08 -0300


--AqsLC8rIMeq19msA
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Fri, May 02, 2003 at 12:14:05AM +0100, Andy Robinson wrote:
| I won't have much time to look at paragraph implementations
| until the weekend, but coding seemed more fun than working 
| tonight :-)  I added one think which I think will really help
| (and would be quite hard to plug in if you don't know
| our main loop). 
| 
| There is now a concept of "relative indentation" or
| "context-sensitive indentation" in Platypus.  One thing
| reST and HTML rendering need is nested lists.  There is
| a runnable example in
|   reportlab/test/test_platypus_indents.py
| which produces
|   reportlab/test/test_platypus_indents.pdf
| 
| 
| Let's say you want to do this in Platypus:
| 
| - level 1 bullet
| - level 1 bullet again
|    - level 2 bullet
|    - level 2 bullet again
|    Definition:
|       Paragraph at this level of nexting
| 
| You would have to make a style for 'level 1 bullet para',
| 'level 2 bullet para' etc, track how indented you need to
| be and dynamically select the right style.  What's missing 
| is the ability to say "indent all stuff below here by 36 points" 
| as you enter a list, and dedent again afterwards as you exit it.
| If you are rendering HTML, you would do this when you saw the
| <ul> and </ul>, and then use just one style for the bullets.
| 
| I added an Indenter ActionFlowable, and modified the main
| loop slightly.  Indenter takes RELATIVE arguments which
| modify the left and right margin of the frame, until the
| next Indenter appears. So you can now do this:
| 
| story.append(Indenter(left=36, right=0) )
| story.append('bullet point 1', bulletStyle) )
| story.append('bullet point 2', bulletStyle) )
| story.append(Indenter(left=36, right=0) )
| story.append('nested bullet point 1', bulletStyle) )
| story.append(Indenter(left=-72, right=0) )
| 
| To do this, frames have two new private attributes
| _leftExtraIndent and _rightExtraIndent, and these are
| set by an Indenter.  They 'carry over' from one 
| frame to the next (and AFAICT work OK when switching
| templates).
| 
| reST and HTML rendering will need a bunch of other stuff too
| but I hope this helps...

Indeed it helps! Though the new para.py supports <ul> and friends, its
output its not nice or controllable. Im still struggling to make it
use different bullets for different levels of identation.

Ive did a initial cleanup on the para.py module, mostly style cleaning
and whitespace. Attached im sending the diff. 

Here you can see a very raw sample of the current state of the writer:

http://dev.x3ng.com.br/Members/sidnei/rstIntro.pdf

There is still a lot of work to do, specially on the template and
footnotes, but Im getting there :)

Next thing on my tasklist is update the writer to use the new Indenter
flowable.

-- 
Sidnei da Silva (dreamcatcher) <sidnei@x3ng.com.br>
X3ng Web Technology <http://www.x3ng.com.br>
GNU/Linux user 257852
Debian GNU/Linux 3.0 (Sid) 2.4.18 ppc

COBOL is for morons.
		-- E.W. Dijkstra

--AqsLC8rIMeq19msA
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="rlab.diff"

? __init__.pyc
? build
? rl_config.pyc
? rlab.diff
? lib/PyFontify.pyc
? lib/__init__.pyc
? lib/abag.pyc
? lib/colors.pyc
? lib/enums.pyc
? lib/fonts.pyc
? lib/logger.pyc
? lib/pagesizes.pyc
? lib/rparsexml.pyc
? lib/sequencer.pyc
? lib/styles.pyc
? lib/units.pyc
? lib/utils.pyc
? lib/xmllib.pyc
? pdfbase/__init__.pyc
? pdfbase/_fontdata.pyc
? pdfbase/pdfdoc.pyc
? pdfbase/pdfmetrics.pyc
? pdfbase/pdfutils.pyc
? pdfgen/__init__.pyc
? pdfgen/canvas.pyc
? pdfgen/pathobject.pyc
? pdfgen/pdfgeom.pyc
? pdfgen/textobject.pyc
? platypus/__init__.pyc
? platypus/doctemplate.pyc
? platypus/flowables.pyc
? platypus/frames.pyc
? platypus/para.pyc
? platypus/paragraph.pyc
? platypus/paraparser.pyc
? platypus/tables.pyc
? platypus/xpreformatted.pyc
Index: pdfgen/canvas.py
===================================================================
RCS file: /cvsroot/reportlab/reportlab/pdfgen/canvas.py,v
retrieving revision 1.111
diff -u -r1.111 canvas.py
--- pdfgen/canvas.py	10 Apr 2003 00:01:17 -0000	1.111
+++ pdfgen/canvas.py	2 May 2003 13:31:54 -0000
@@ -429,13 +429,13 @@
         is to keep the user's current zoom settings. the last
         arguments may or may not be needed depending on the
         choice of 'fitType'.
-        
+
         Fit types and the other arguments they use are:
         /XYZ left top zoom - fine grained control.  null
           or zero for any of the parameters means 'leave
           as is', so "0,0,0" will keep the reader's settings.
           NB. Adobe Reader appears to prefer "null" to 0's.
-          
+
         /Fit - entire page fits in window
 
         /FitH top - top coord at top of window, width scaled
@@ -443,7 +443,7 @@
 
         /FitV left - left coord at left of window, height
                      scaled to fit
-                     
+
         /FitR left bottom right top - scale window to fit
                                   the specified rectangle
 
@@ -464,7 +464,7 @@
             right = "null"
         if zoom is None:
             zoom = "null"
-        
+
         if fitType == "XYZ":
             dest.xyz(left,top,zoom)
         elif fitType == "Fit":
@@ -483,7 +483,7 @@
         elif fitType == "FitBV":
             dest.fitbv(left)
         else:
-            raise "Unknown Fit type %s" % (fitType,)           
+            raise "Unknown Fit type %s" % (fitType,)
 
         dest.setPage(pageref)
         return dest
@@ -498,7 +498,7 @@
            the page."""
         #This method should probably be deprecated since it is just a sub-set of bookmarkPage
         return self.bookmarkPage(key,fitType="FitH",top=yhorizontal)
-                    
+
     def bookmarkHorizontal(self, key, relativeX, relativeY):
         """w.r.t. the current transformation, bookmark this horizontal."""
         (xt, yt) = self.absolutePosition(relativeX,relativeY)
@@ -829,7 +829,7 @@
         assert rot % 90.0 == 0.0, "Rotation must be a multiple of 90 degrees"
         self._pageRotation = rot
 
-        
+
     def addLiteral(self, s, escaped=1):
         """introduce the literal text of PDF operations s into the current stream.
            Only use this if you are an expert in the PDF file format."""
Index: platypus/para.py
===================================================================
RCS file: /cvsroot/reportlab/reportlab/platypus/para.py,v
retrieving revision 1.6
diff -u -r1.6 para.py
--- platypus/para.py	9 Apr 2003 22:58:37 -0000	1.6
+++ platypus/para.py	2 May 2003 13:32:02 -0000
@@ -63,7 +63,8 @@
 from reportlab.platypus.flowables import Flowable
 from reportlab.lib import colors
 
-import string
+from types import StringType, UnicodeType, InstanceType, TupleType, ListType, FloatType
+from string import letters as LETTERS, whitespace as WHITESPACE
 
 # SET THIS TO CAUSE A VIEWING BUG WITH ACROREAD 3 (for at least one input)
 # CAUSEERROR = 0
@@ -91,7 +92,6 @@
             program = []
         self.lineOpHandlers = [] # for handling underlining and hyperlinking, etc
         self.program = program
-        #self.
         self.indent = self.rightIndent = 0.0
         self.baseindent = 0.0 # adjust this to add more indentation for bullets, eg
         self.fontName = "Helvetica"
@@ -102,10 +102,12 @@
         from reportlab.lib.enums import TA_LEFT
         self.alignment = TA_LEFT
         self.textStateStack = []
+
     TEXT_STATE_VARIABLES = ("indent", "rightIndent", "fontName", "fontSize",
                             "leading", "fontColor", "lineOpHandlers", "rise",
                             "alignment")
                             #"textStateStack")
+
     def pushTextState(self):
         state = []
         for var in self.TEXT_STATE_VARIABLES:
@@ -116,6 +118,7 @@
         #print "push", self.textStateStack
         #print "push", len(self.textStateStack), state
         return state
+
     def popTextState(self):
         state = self.textStateStack[-1]
         self.textStateStack = self.textStateStack[:-1]
@@ -136,7 +139,6 @@
         remainder = program[:]
         #program1 = remainder[:] # debug only
         lineprogram = []
-        from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
         #if maxheight<TOOSMALLSPACE:
         #    raise ValueError, "attempt to format inside too small a height! "+repr(maxheight)
         heightremaining = maxheight-leading
@@ -150,9 +152,11 @@
             linewidth = maxwidth - indent - rightIndent
             beforelinestate = self.__dict__.copy()
             if linewidth<TOOSMALLSPACE:
-                raise ValueError, "indents %s %s too wide for space %s" % (self.indent, self.rightIndent, maxwidth)
+                raise ValueError, "indents %s %s too wide for space %s" % (self.indent, self.rightIndent, \
+                                                                           maxwidth)
             try:
-                (lineIsFull, line, cursor, currentLength, usedIndent, maxLength, justStrings) = self.fitLine(remainder, maxwidth)
+                (lineIsFull, line, cursor, currentLength, \
+                 usedIndent, maxLength, justStrings) = self.fitLine(remainder, maxwidth)
             except:
 ##                print "failed to fit line near", cursorcount # debug
 ##                for i in program1[max(0,cursorcount-10): cursorcount]:
@@ -235,20 +239,23 @@
         self.__dict__.update(startstate)
         heightused = maxheight - heightremaining
         return (lineprogram, remainder, laststate, heightused)
+
     def getState(self):
         # inlined
         return self.__dict__.copy()
+
     def resetState(self, state):
         # primarily inlined
         self.__dict__.update(state)
+
 ##    def sizeOfWord(self, word):
 ##        inlineThisFunctionForEfficiency
 ##        return float(stringWidth(word, self.fontName, self.fontSize))
+
     def fitLine(self, program, totalLength):
         "fit words (and other things) onto a line"
         # assuming word lengths and spaces have not been yet added
         # fit words onto a line up to maxlength, adding spaces and respecting extra space
-        from types import StringType, TupleType, InstanceType, FloatType
         from reportlab.pdfbase.pdfmetrics import stringWidth
         usedIndent = self.indent
         maxLength = totalLength - usedIndent - self.rightIndent
@@ -269,7 +276,7 @@
             opcode = program[cursor]
             #if debug: print "opcode", cursor, opcode
             topcode = type(opcode)
-            if topcode is StringType or topcode is InstanceType:
+            if topcode in (StringType, UnicodeType, InstanceType):
                 lastneedspace = needspace
                 needspace = 0
                 if topcode is InstanceType:
@@ -278,7 +285,7 @@
                     needspace = 0
                 else:
                     saveopcode = opcode
-                    opcode = opcode.strip() #string.strip(opcode)
+                    opcode = opcode.strip()
                     if opcode:
                         width = stringWidth(opcode, fontName, fontSize)
                     else:
@@ -342,7 +349,7 @@
                     oldcolor = self.fontColor
                     (i, colorname) = opcode
                     #print "opcode", opcode
-                    if type(colorname) is StringType:
+                    if type(colorname) in (StringType, UnicodeType):
                         color = self.fontColor = getattr(colors, colorname)
                     else:
                         color = self.fontColor = colorname # assume its something sensible :)
@@ -357,7 +364,7 @@
                     # change font size
                     (i, fontsize) = opcode
                     size = abs(float(fontsize))
-                    if type(fontsize) is StringType:
+                    if type(fontsize) in (StringType, UnicodeType):
                         if fontsize[:1]=="+":
                             fontSize = self.fontSize = self.fontSize + size
                         elif fontsize[:1]=="-":
@@ -457,41 +464,43 @@
             line.append( ("nextLine", 0) )
         #print "fitline", line
         return (lineIsFull, line, cursor, currentLength, usedIndent, maxLength, justStrings)
+
     def centerAlign(self, line, lineLength, maxLength):
         diff = maxLength-lineLength
         shift = diff/2.0
         if shift>TOOSMALLSPACE:
             return self.insertShift(line, shift)
         return line
+
     def rightAlign(self, line, lineLength, maxLength):
         shift = maxLength-lineLength
         #die
         if shift>TOOSMALLSPACE:
             return self.insertShift(line, shift)
         return line
+
     def insertShift(self, line, shift):
         # insert shift just before first visible element in line
-        from types import StringType, InstanceType
         result = []
         first = 1
         for e in line:
             te = type(e)
-            if first and (te is StringType or te is InstanceType):
+            if first and (te in (StringType, UnicodeType, InstanceType)):
                 result.append(shift)
                 first = 0
             result.append(e)
         return result
+
     def justifyAlign(self, line, lineLength, maxLength):
         diff = maxLength-lineLength
         # count EXPANDABLE SPACES AFTER THE FIRST VISIBLE
-        from types import InstanceType, StringType, TupleType, FloatType
         spacecount = 0
         visible = 0
         for e in line:
             te = type(e)
             if te is FloatType and e>TOOSMALLSPACE and visible:
                 spacecount = spacecount+1
-            elif te is StringType or te is InstanceType:
+            elif te in (StringType, UnicodeType, InstanceType):
                 visible = 1
         #if debug: print "diff is", diff, "wordcount", wordcount #; die
         if spacecount<1:
@@ -509,7 +518,7 @@
             e = line[cursor]
             te = type(e)
             result.append(e)
-            if (te is InstanceType or te is StringType):
+            if (te in (StringType, UnicodeType, InstanceType)):
                 visible = 1
             elif te is FloatType and e>TOOSMALLSPACE and visible:
                 expanded = e+shift
@@ -545,25 +554,23 @@
 ##                first = 0
 ##            cursor = cursor+1
 ##        return result
+
     def shrinkWrap(self, line):
         # for non justified text, collapse adjacent text/shift's into single operations
-        #return line # for testing
         result = []
         index = 0
         maxindex = len(line)
-        from types import FloatType, StringType, InstanceType
-        from string import join
         while index<maxindex:
             e = line[index]
             te = type(e)
-            if te is StringType and index<maxindex-1:
+            if te in (StringType, UnicodeType) and index<maxindex-1:
                 # collect strings and floats
                 thestrings = [e]
                 thefloats = 0.0
                 index = index+1
                 nexte = line[index]
                 tnexte = type(nexte)
-                while index<maxindex and (tnexte is FloatType or tnexte is StringType):
+                while index<maxindex and (tnexte in (FloatType, StringType, UnicodeType)):
                     # switch to expandable space if appropriate
                     if tnexte is FloatType:
                         if thefloats<0 and nexte>0:
@@ -571,14 +578,14 @@
                         if nexte<0 and thefloats>0:
                             nexte = -nexte
                         thefloats = thefloats + nexte
-                    elif tnexte is StringType:
+                    elif tnexte in (StringType, UnicodeType):
                         thestrings.append(nexte)
                     index = index+1
                     if index<maxindex:
                         nexte = line[index]
                         tnexte = type(nexte)
                 # wrap up the result
-                s = string.join(thestrings)
+                s = ' '.join(thestrings)
                 result.append(s)
                 result.append(float(thefloats))
                 # back up for unhandled element
@@ -588,12 +595,12 @@
             index = index+1
 
         return result
+
     def cleanProgram(self, line):
         "collapse adjacent spacings"
         #return line # for debugging
         result = []
         last = 0
-        from types import FloatType, TupleType, StringType, InstanceType
         for e in line:
             if type(e) is FloatType:
                 # switch to expandable space if appropriate
@@ -634,7 +641,9 @@
                 tthis = type(this)
                 tnext = type(next)
                 # don't swap visibles
-                if tthis is StringType or tnext is StringType or this is InstanceType or tnext is InstanceType:
+                if tthis in (StringType, UnicodeType) or \
+                   tnext in (StringType, UnicodeType) or \
+                   this is InstanceType or tnext is InstanceType:
                     doswap = 0
                 # only swap two tuples if the second one is an end operation and the first is something else
                 elif tthis is TupleType:
@@ -654,10 +663,10 @@
                     result[nextindex] = this
                     change = 1
         return result
+
     def runOpCodes(self, program, canvas, textobject):
         "render the line(s)"
-        #import types
-        from types import StringType, TupleType, InstanceType, FloatType
+
         escape = canvas._escape
         code = textobject._code
         startstate = self.__dict__.copy()
@@ -672,7 +681,7 @@
         indented = 0
         for opcode in program:
             topcode = type(opcode)
-            if topcode is StringType or topcode is InstanceType:
+            if topcode in (StringType, UnicodeType, InstanceType):
                 if not indented:
                     if abs(thislineindent)>TOOSMALLSPACE:
                         #if debug: print "INDENTING", thislineindent
@@ -688,7 +697,7 @@
                     font = self.fontName
                     size = self.fontSize
                     textobject.setFont(font, size)
-                if topcode is StringType:
+                if topcode in (StringType, UnicodeType):
                     #textobject.textOut(opcode)
                     text = escape(opcode)
                     code.append('(%s) Tj' % text)
@@ -723,7 +732,7 @@
                     oldcolor = self.fontColor
                     (i, colorname) = opcode
                     #print "opcode", opcode
-                    if type(colorname) is StringType:
+                    if type(colorname) in (StringType, UnicodeType):
                         color = self.fontColor = getattr(colors, colorname)
                     else:
                         color = self.fontColor = colorname # assume its something sensible :)
@@ -744,7 +753,7 @@
                     # change font size
                     (i, fontsize) = opcode
                     size = abs(float(fontsize))
-                    if type(fontsize) is StringType:
+                    if type(fontsize) in (StringType, UnicodeType):
                         if fontsize[:1]=="+":
                             fontSize = self.fontSize = self.fontSize + size
                         elif fontsize[:1]=="-":
@@ -834,12 +843,12 @@
 
 def stringLine(line, length):
     "simple case: line with just strings and spacings which can be ignored"
+
     strings = []
-    from types import StringType
     for x in line:
-        if type(x) is StringType:
+        if type(x) in (StringType, UnicodeType):
             strings.append(x)
-    text = string.join(strings)
+    text = ' '.join(strings)
     result = [text, float(length)]
     nextlinemark = ("nextLine", 0)
     if line and line[-1]==nextlinemark:
@@ -848,14 +857,14 @@
 
 def simpleJustifyAlign(line, currentLength, maxLength):
     "simple justification with only strings"
+
     strings = []
-    from types import StringType
     for x in line[:-1]:
-        if type(x) is StringType:
+        if type(x) in (StringType, UnicodeType):
             strings.append(x)
     nspaces = len(strings)-1
     slack = maxLength-currentLength
-    text = string.join(strings)
+    text = ' '.join(strings)
     if nspaces>0 and slack>0:
         wordspacing = slack/float(nspaces)
         result = [("wordSpacing", wordspacing), text, maxLength, ("wordSpacing", 0)]
@@ -869,16 +878,15 @@
 from reportlab.lib.colors import black
 
 def readBool(text):
-    if string.upper(text) in ("Y", "YES", "TRUE", "1"):
+    if text.upper() in ("Y", "YES", "TRUE", "1"):
         return 1
-    elif string.upper(text) in ("N", "NO", "FALSE", "0"):
+    elif text.upper() in ("N", "NO", "FALSE", "0"):
         return 0
     else:
         raise RMLError, "true/false attribute has illegal value '%s'" % text
 
 def readAlignment(text):
-    from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
-    up = string.upper(text)
+    up = text.upper()
     if up == 'LEFT':
         return TA_LEFT
     elif up == 'RIGHT':
@@ -891,13 +899,13 @@
 def readLength(text):
     """Read a dimension measurement: accept "3in", "5cm",
     "72 pt" and so on."""
-    text = string.strip(text)
+    text = text.strip()
     try:
         return float(text)
     except ValueError:
-        text = string.lower(text)
+        text = text.lower()
         numberText, units = text[:-2],text[-2:]
-        numberText = string.strip(numberText)
+        numberText = numberText.strip()
         try:
             number = float(numberText)
         except ValueError:
@@ -916,13 +924,12 @@
 
 def lengthSequence(s, converter=readLength):
     """from "(2, 1)" or "2,1" return [2,1], for example"""
-    from string import split, strip
-    s = strip(s)
+    s = s.strip()
     if s[:1]=="(" and s[-1:]==")":
         s = s[1:-1]
-    sl = split(s, ",")
-    sl = map(strip, sl)
-    sl = map(converter, sl)
+    sl = s.split(',')
+    sl = [s.strip() for s in sl]
+    sl = [converter(s) for s in sl]
     return sl
 
 
@@ -931,7 +938,7 @@
     if not text:
         return None
     from reportlab.lib import colors
-    if text[0] in string.letters:
+    if text[0] in LETTERS:
         return colors.__dict__[text]
     tup = lengthSequence(text)
 
@@ -978,6 +985,7 @@
     bulletIndent=0
     textColor=black
     backColor=None
+
     def __init__(self, name, parent=None, **kw):
         mydict = self.__dict__
         if parent:
@@ -985,6 +993,7 @@
                 mydict[a]=b
         for (a,b) in kw.items():
             mydict[a] =  b
+
     def addAttributes(self, dictionary):
         for key in dictionary.keys():
             value = dictionary[key]
@@ -999,14 +1008,21 @@
     "h1.defaultStyle": "Heading1",
     "h2.defaultStyle": "Heading2",
     "h3.defaultStyle": "Heading3",
+    "h4.defaultStyle": "Heading4",
+    "h5.defaultStyle": "Heading5",
+    "h6.defaultStyle": "Heading6",
     "title.defaultStyle": "Title",
+    "subtitle.defaultStyle": "SubTitle",
     "para.defaultStyle": "Normal",
     "pre.defaultStyle": "Code",
-    "li.defaultStyle": "Definition"
+    "ul.defaultStyle": "UnorderedList",
+    "ol.defaultStyle": "OrderedList",
+    "li.defaultStyle": "Definition",
     }
 
 class FastPara(Flowable):
     "paragraph with no special features (not even a single ampersand!)"
+
     def __init__(self, style, simpletext):
         #if debug:
         #    print "FAST", id(self)
@@ -1015,6 +1031,7 @@
         self.style = style
         self.simpletext = simpletext
         self.lines = None
+
     def wrap(self, availableWidth, availableHeight):
         simpletext = self.simpletext
         self.availableWidth = availableWidth
@@ -1027,7 +1044,7 @@
         size = style.fontSize
         firstindent = style.firstLineIndent
         #textcolor = style.textColor
-        words = string.split(simpletext)
+        words = simpletext.split()
         lines = []
         from reportlab.pdfbase.pdfmetrics import stringWidth
         spacewidth = stringWidth(" ", font, size)
@@ -1062,18 +1079,18 @@
                     #print "currentline", currentline
                 else:
                     # emit the line
-                    lines.append( (string.join(currentline), currentlength, len(currentline)) )
+                    lines.append( (' '.join(currentline), currentlength, len(currentline)) )
                     currentline = []
                     currentlength = 0
                     heightused = heightused+leading
                     if heightused+leading>availableHeight:
                         done = 1
             if currentlength and not done:
-                lines.append( (string.join(currentline), currentlength, len(currentline) ))
+                lines.append( (' '.join(currentline), currentlength, len(currentline) ))
                 heightused = heightused+leading
             self.lines = lines
             self.height = heightused
-            remainder = self.remainder = string.join(words[cursor:])
+            remainder = self.remainder = ' '.join(words[cursor:])
             #print "lines", lines
             #print "remainder is", remainder
         else:
@@ -1086,6 +1103,7 @@
             result = (availableWidth, heightused)
         #if debug: print "wrap is", (availableWidth, availableHeight), result, len(lines)
         return result
+
     def split(self, availableWidth, availableHeight):
         style = self.style
         leading = style.leading
@@ -1102,7 +1120,6 @@
             return [self]
 
     def draw(self):
-        from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
         style = self.style
         lines = self.lines
         rightIndent = style.rightIndent
@@ -1158,10 +1175,12 @@
             #textobject.textOut(text)
             y = y-leading
         c.drawText(textobject)
+
     def getSpaceBefore(self):
         #if debug:
         #    print "got space before", self.spaceBefore
         return self.style.spaceBefore
+
     def getSpaceAfter(self):
         #print "got space after", self.spaceAfter
         return self.style.spaceAfter
@@ -1174,15 +1193,35 @@
         result[stylenamekey] = styles[stylenamevalue]
     return result
 
+def buildContext(stylesheet=None):
+    result = {}
+    from reportlab.lib.styles import getSampleStyleSheet
+    if stylesheet is not None:
+        # Copy styles with the same name as aliases
+        for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items():
+            if stylesheet.has_key(stylenamekey):
+                result[stylenamekey] = stylesheet[stylenamekey]
+        # Then make aliases
+        for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items():
+            if stylesheet.has_key(stylenamevalue):
+                result[stylenamekey] = stylesheet[stylenamevalue]
+
+    styles = getSampleStyleSheet()
+    # Then, fill in defaults if they were not filled yet.
+    for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items():
+        if not result.has_key(stylenamekey) and styles.has_key(stylenamevalue):
+            result[stylenamekey] = styles[stylenamevalue]
+    return result
+
 class Para(Flowable):
+
     spaceBefore = 0
     spaceAfter = 0
+
     def __init__(self, style, parsedText=None, bulletText=None, state=None, context=None, baseindent=0):
         #print id(self), "para", parsedText
         self.baseindent = baseindent
-        if context is None:
-            context = defaultContext()
-        self.context = context
+        self.context = buildContext(context)
         self.parsedText = parsedText
         self.bulletText = bulletText
         self.style1 = style # make sure Flowable doesn't use this unless wanted! call it style1 NOT style
@@ -1199,15 +1238,18 @@
         #    print "spaceBefore is", self.spaceBefore, self.parsedText
         self.bold = 0
         self.italic = 0
-        self.face = "times"
-        self.size = 10
+        self.face = style.fontName
+        self.size = style.fontSize
+
     def getSpaceBefore(self):
         #if debug:
         #    print "got space before", self.spaceBefore
         return self.spaceBefore
+
     def getSpaceAfter(self):
         #print "got space after", self.spaceAfter
         return self.spaceAfter
+
     def wrap(self, availableWidth, availableHeight):
         if debug:
             print "WRAPPING", id(self), availableWidth, availableHeight
@@ -1242,7 +1284,8 @@
         if not program:
             self.program = program = self.compileProgram(parsedText)
         if not self.formattedProgram:
-            (formattedProgram, remainder, laststate, heightused) = p.format(availableWidth, availableHeight, program, leading)
+            (formattedProgram, remainder, \
+             laststate, heightused) = p.format(availableWidth, availableHeight, program, leading)
             self.formattedProgram = formattedProgram
             self.height = heightused
             self.laststate = laststate
@@ -1259,7 +1302,8 @@
             height = availableHeight + 1
             #print "laststate is", laststate
             #print "saving remainder", remainder
-            self.remainder = Para(self.style1, parsedText=None, bulletText=None, state=laststate)
+            self.remainder = Para(self.style1, parsedText=None, bulletText=None, \
+                                  state=laststate, context=self.context)
             self.remainder.program = remainder
             self.remainder.spaceAfter = self.spaceAfter
             self.spaceAfter = 0
@@ -1277,6 +1321,7 @@
                 print "exact match???" + repr(availableHeight, h)
             print "wrap is", (availableWidth, availableHeight), result
         return result
+
     def split(self, availableWidth, availableHeight):
         #if debug:
         #    print "SPLITTING", id(self), availableWidth, availableHeight
@@ -1300,6 +1345,7 @@
             result= [self]
         #if debug: print "split is", result
         return result
+
     def draw(self):
         p = self.myengine #paragraphEngine()
         formattedProgram = self.formattedProgram
@@ -1362,11 +1408,10 @@
         # now look for a place where to insert the unindent after the first line
         if style.firstLineIndent:
             count = 0
-            from types import StringType, InstanceType
             for x in program:
                 count = count+1
                 tx = type(x)
-                if tx is StringType or tx is InstanceType:
+                if tx in (StringType, UnicodeType, InstanceType):
                     break
             program.insert( count, ("indent", -style.firstLineIndent ) ) # defaults to end if no visibles
         #print "="*8, id(self), "program is"
@@ -1376,7 +1421,6 @@
 ##        # check pushes and pops
 ##        stackcount = 0
 ##        dump = 0
-##        from types import TupleType
 ##        for x in program:
 ##            if dump:
 ##                print "dump:", x
@@ -1393,6 +1437,7 @@
 ##                    print "STACK UNDERFLOW!"
 ##        if dump: stop
         return program
+
     def linearize(self, program = None, parsedText=None):
         #print "LINEARIZING", self
         #program = self.program = []
@@ -1417,23 +1462,24 @@
             program.append( ("leading", 0) )
         program.append( ("nextLine", 0) )
         program.append( ("pop",) )
+
     def compileComponent(self, parsedText, program):
         import types
         ttext = type(parsedText)
         #program = self.program
-        if ttext is types.StringType:
+        if ttext in (StringType, UnicodeType):
             # handle special characters here...
             # short cut
             if parsedText:
-                stext = parsedText.strip() #string.strip(parsedText)
+                stext = parsedText.strip()
                 if not stext:
                     program.append(" ") # contract whitespace to single space
                 else:
                     handleSpecialCharacters(self, parsedText, program)
-        elif ttext is types.ListType:
+        elif ttext is ListType:
             for e in parsedText:
                 self.compileComponent(e, program)
-        elif ttext is types.TupleType:
+        elif ttext is TupleType:
             (tagname, attdict, content, extra) = parsedText
             if not attdict:
                 attdict = {}
@@ -1455,10 +1501,11 @@
                         a("</%s>" % tagname)
                     else:
                         a("/>")
-                    t = string.join(L, "")
+                    t = ''.join(L)
                     handleSpecialCharacters(self, t, program)
                 else:
                     raise ValueError, "don't know how to handle tag " + repr(tagname)
+
     def shiftfont(self, program, face=None, bold=None, italic=None):
         oldface = self.face
         oldbold = self.bold
@@ -1482,18 +1529,22 @@
         for e in content:
             self.compileComponent(e, program)
     #compile_para = compile_ # at least for now...
+
     def compile_pageNumber(self, attdict, content, extra, program):
         program.append(PageNumberObject())
+
     def compile_b(self, attdict, content, extra, program):
         (f,b,i) = self.shiftfont(program, bold=1)
         for e in content:
             self.compileComponent(e, program)
         self.shiftfont(program, bold=b)
+
     def compile_i(self, attdict, content, extra, program):
         (f,b,i) = self.shiftfont(program, italic=1)
         for e in content:
             self.compileComponent(e, program)
         self.shiftfont(program, italic=i)
+
     def compile_u(self, attdict, content, extra, program):
         # XXXX must eventually add things like alternative colors
         #program = self.program
@@ -1501,6 +1552,7 @@
         for e in content:
             self.compileComponent(e, program)
         program.append( ('endLineOperation', UNDERLINE) )
+
     def compile_sub(self, attdict, content, extra, program):
         size = self.size
         self.size = newsize = size * 0.7
@@ -1520,13 +1572,12 @@
         atts = attdict.copy()
         bulletmaker = bulletMaker(tagname, atts, self.context)
         # now do each element as a separate paragraph
-        import types
         for e in content:
             te = type(e)
-            if te is types.StringType:
-                if e.strip(): #string.strip(e):
+            if te in (StringType, UnicodeType):
+                if e.strip():
                     raise ValueError, "don't expect CDATA between list elements"
-            elif te is types.TupleType:
+            elif te is TupleType:
                 (tagname, attdict1, content1, extra) = e
                 if tagname!="li":
                     raise ValueError, "don't expect %s inside list" % repr(tagname)
@@ -1548,33 +1599,31 @@
         atts = attdict.copy()
         bulletmaker = bulletMaker("dl", atts, self.context)
         # now do each element as a separate paragraph
-        import types
         contentcopy = list(content) # copy for destruction
         bullet = ""
         while contentcopy:
             e = contentcopy[0]
             del contentcopy[0]
             te = type(e)
-            if te is types.StringType:
-                if e.strip(): #string.strip(e):
+            if te in (StringType, UnicodeType):
+                if e.strip():
                     raise ValueError, "don't expect CDATA between list elements"
                 elif not contentcopy:
                     break # done at ending whitespace
                 else:
                     continue # ignore intermediate whitespace
-            elif te is types.TupleType:
+            elif te is TupleType:
                 (tagname, attdict1, content1, extra) = e
                 if tagname!="dd" and tagname!="dt":
-                    raise ValueError, "don't expect %s here inside list, expect 'dd' or 'dt'" % repr(tagname)
+                    raise ValueError, "don't expect %s here inside list, expect 'dd' or 'dt'" % \
+                          repr(tagname)
                 if tagname=="dt":
                     if bullet:
                         raise ValueError, "dt will not be displayed unless followed by a dd: "+repr(bullet)
                     if content1:
-                        if len(content1)!=1:
-                            raise ValueError, "only simple strings supported in dd content currently: "+repr(content1)
-                        bullet = content1[0]
-                        if type(bullet) is not types.StringType:
-                            raise ValueError, "only simple strings supported in dd content currently: "+repr(content1)
+                        self.compile_para(attdict1, content1, extra, program)
+                        # raise ValueError, \
+                        # "only simple strings supported in dd content currently: "+repr(content1)
                 elif tagname=="dd":
                     newatts = atts.copy()
                     if attdict1:
@@ -1597,6 +1646,7 @@
         program.append( ('size', size) )
         self.size = size
         program.append( ('rise', -rise) )
+
     def compile_font(self, attdict, content, extra, program):
         #program = self.program
         program.append( ("push",) ) # store current data
@@ -1618,6 +1668,7 @@
         for e in content:
             self.compileComponent(e, program)
         program.append( ("pop",) ) # restore as before
+
     def compile_a(self, attdict, content, extra, program):
         url = attdict["href"]
         colorname = attdict.get("color", "blue")
@@ -1632,6 +1683,7 @@
         program.append( ('endLineOperation', UNDERLINE) )
         program.append( ('endLineOperation', Link) )
         program.append( ("pop",) ) # restore as before
+
     def compile_link(self, attdict, content, extra, program):
         dest = attdict["destination"]
         colorname = attdict.get("color", None)
@@ -1647,6 +1699,7 @@
         program.append( ('endLineOperation', UNDERLINE) )
         program.append( ('endLineOperation', Link) )
         program.append( ("pop",) ) # restore as before
+
     def compile_setLink(self, attdict, content, extra, program):
         dest = attdict["destination"]
         colorname = attdict.get("color", "blue")
@@ -1664,16 +1717,18 @@
             program.append( ('endLineOperation', UNDERLINE) )
         program.append( ('endLineOperation', Link) )
         program.append( ("pop",) ) # restore as before
+
     #def compile_p(self, attdict, content, extra, program):
     #    # have to be careful about base indent here!
     #    not finished
+
     def compile_bullet(self, attdict, content, extra, program):
-        from types import StringType
         ### eventually should allow things like images and graphics in bullets too XXXX
-        if len(content)!=1 or type(content[0]) is not StringType:
+        if len(content)!=1 or type(content[0]) not in (StringType, UnicodeType):
             raise ValueError, "content for bullet must be a single string"
         text = content[0]
         self.do_bullet(text, program)
+
     def do_bullet(self, text, program):
         style = self.style1
         #program = self.program
@@ -1681,26 +1736,37 @@
         font = style.bulletFontName
         size = style.bulletFontSize
         program.append( ("bullet", text, indent, font, size) )
+
     def compile_tt(self, attdict, content, extra, program):
         (f,b,i) = self.shiftfont(program, face="Courier")
         for e in content:
             self.compileComponent(e, program)
         self.shiftfont(program, face=f)
+
     def compile_greek(self, attdict, content, extra, program):
         self.compile_font({"face": "symbol"}, content, extra, program)
+
     def compile_evalString(self, attdict, content, extra, program):
         program.append( EvalStringObject(attdict, content, extra, self.context) )
+
     def compile_name(self, attdict, content, extra, program):
         program.append( NameObject(attdict, content, extra, self.context) )
+
     def compile_getName(self, attdict, content, extra, program):
         program.append( GetNameObject(attdict, content, extra, self.context) )
+
     def compile_seq(self, attdict, content, extra, program):
         program.append( SeqObject(attdict, content, extra, self.context) )
+
     def compile_seqReset(self, attdict, content, extra, program):
         program.append( SeqResetObject(attdict, content, extra, self.context) )
+
     def compile_seqDefault(self, attdict, content, extra, program):
         program.append( SeqDefaultObject(attdict, content, extra, self.context) )
+
     def compile_para(self, attdict, content, extra, program, stylename = "para.defaultStyle"):
+        if attdict is None:
+            attdict = {}
         context = self.context
         stylename = attdict.get("style", stylename)
         style = context[stylename]
@@ -1708,22 +1774,15 @@
         newstyle.addAttributes(attdict)
         bulletText = attdict.get("bulletText", None)
         mystyle = self.style1
-        #newstyle.bulletIndent = mystyle.leftIndent+newstyle.bulletIndent
-        #print "attdict", attdict
-        #print "leftindent, baseindent", mystyle.leftIndent
-        #print "bulletIndent", newstyle.bulletIndent
-        thepara = Para(newstyle, content, context=context, bulletText=bulletText) # possible ref loop on context, break later
+        thepara = Para(newstyle, content, context=context, bulletText=bulletText)
+        # possible ref loop on context, break later
         # now compile it and add it to the program
         mybaseindent = self.baseindent
         self.baseindent = thepara.baseindent = mystyle.leftIndent + self.baseindent
         thepara.linearize(program=program)
-        #print "program so far"
-        #for x in program:
-        #    print x
         program.append( ("nextLine", 0) )
         self.baseindent = mybaseindent
 
-
 class bulletMaker:
     def __init__(self, tagname, atts, context):
         self.tagname = tagname
@@ -1742,6 +1801,7 @@
             indent = stringWidth("XXX", "Courier", size)
             atts["leftIndent"] = str(indent)
         self.count = 0
+
     def makeBullet(self, atts, bl=None):
         count = self.count = self.count+1
         typ = self.typ
@@ -1778,7 +1838,9 @@
 
 class EvalStringObject:
     "this will only work if rml2pdf is present"
+
     tagname = "evalString"
+
     def __init__(self, attdict, content, extra, context):
         if not attdict:
             attdict = {}
@@ -1786,11 +1848,13 @@
         self.content = content
         self.context = context
         self.extra = extra
+
     def getOp(self, tuple, engine):
         from rlextra.rml2pdf.rml2pdf import Controller
         #print "tuple", tuple
         op = self.op = Controller.processTuple(tuple, self.context, {})
         return op
+
     def width(self, engine):
         from reportlab.pdfbase.pdfmetrics import stringWidth
         content = self.content
@@ -1803,10 +1867,12 @@
         #print self
         s = str(op)
         return stringWidth(s, engine.fontName, engine.fontSize)
+
     def execute(self, engine, textobject, canvas):
         textobject.textOut(str(self.op))
 
 class SeqObject(EvalStringObject):
+
     def getOp(self, tuple, engine):
         from reportlab.lib.sequencer import getSequencer
         globalsequencer = getSequencer()
@@ -1830,6 +1896,7 @@
         pass # name doesn't produce any output
 
 class SeqDefaultObject(NameObject):
+
     def getOp(self, tuple, engine):
         from reportlab.lib.sequencer import getSequencer
         globalsequencer = getSequencer()
@@ -1843,6 +1910,7 @@
         return ""
 
 class SeqResetObject(NameObject):
+
     def getOp(self, tuple, engine):
         from reportlab.lib.sequencer import getSequencer
         import math
@@ -1864,11 +1932,14 @@
     tagname = "getName"
 
 class PageNumberObject:
+
     def __init__(self, example="XXX"):
         self.example = example # XXX SHOULD ADD THE ABILITY TO PASS IN EXAMPLES
+
     def width(self, engine):
         from reportlab.pdfbase.pdfmetrics import stringWidth
         return stringWidth(self.example, engine.fontName, engine.fontSize)
+
     def execute(self, engine, textobject, canvas):
         n = canvas.getPageNumber()
         textobject.textOut(str(n))
@@ -1894,7 +1965,7 @@
             result = None
             if not bulletText and len(content)==1:
                 text = content[0]
-                if type(text) is types.StringType and "&" not in text:
+                if type(text) in (StringType, UnicodeType) and "&" not in text:
                     result = FastPara(mystyle, text)
             if result is None:
                 result = Para(mystyle, content, context=context, bulletText=bulletText) # possible ref loop on context, break later
@@ -2119,7 +2190,7 @@
     for fragment in amptext:
         if not first:
             # check for special chars
-            semi = string.find(fragment, ";")
+            semi = fragment.find(";")
             if semi>0:
                 name = fragment[:semi]
                 if greeks.has_key(name):
@@ -2128,7 +2199,7 @@
                     (f,b,i) = engine.shiftfont(program, face="symbol")
                     program.append(greeksub)
                     engine.shiftfont(program, face=f)
-                    if fragment and fragment[0] in string.whitespace:
+                    if fragment and fragment[0] in WHITESPACE:
                         program.append(" ") # follow the greek with a space
                 else:
                     # add back the &
@@ -2143,10 +2214,10 @@
         # does the last one need a space?
         if sfragment and fragment:
             # reader 3 used to go nuts if you don't special case the last frag, but it's fixed?
-            if fragment[-1] in string.whitespace: # or fragment==lastfrag:
+            if fragment[-1] in WHITESPACE: # or fragment==lastfrag:
                 program.append( sfragment[-1]+" " )
             else:
-                last = sfragment[-1].strip() #string.strip(sfragment[-1])
+                last = sfragment[-1].strip()
                 if last:
                     #print "last is", repr(last)
                     program.append( last )
@@ -2154,7 +2225,7 @@
     #print "HANDLED", program
     return program
 
-def Paragraph(text, style, bulletText=None, frags=None):
+def Paragraph(text, style, bulletText=None, frags=None, context=None):
     """ Paragraph(text, style, bulletText=None)
     intended to be like a platypus Paragraph but better.
     """
@@ -2165,7 +2236,7 @@
         # use the fully featured one.
         from reportlab.lib import rparsexml
         parsedpara = rparsexml.parsexmlSimple(text)
-        return Para(style, parsedText=parsedpara, bulletText=bulletText, state=None)
+        return Para(style, parsedText=parsedpara, bulletText=bulletText, state=None, context=context)
 
 ##class Paragraph(Para):
 ##    """ Paragraph(text, style, bulletText=None)
@@ -2206,8 +2277,10 @@
 UNDERLINE = UnderLineHandler()
 
 class HotLink(UnderLineHandler):
+
     def __init__(self, url):
         self.url = url
+
     def end_at(self, x, y, para, canvas, textobject):
         fontsize = para.fontSize
         rect = [self.xStart, self.yStart, x,y+fontsize]
@@ -2215,27 +2288,31 @@
             print "LINKING RECTANGLE", rect
             #canvas.rect(self.xStart, self.yStart, x-self.xStart,y+fontsize-self.yStart, stroke=1)
         self.link(rect, canvas)
+
     def link(self, rect, canvas):
         canvas.linkURL(self.url, rect, relative=1)
 
 class InternalLink(HotLink):
+
     def link(self, rect, canvas):
         destinationname = self.url
         contents = ""
         canvas.linkRect(contents, destinationname, rect, Border="[0 0 0]")
 
 class DefDestination(HotLink):
+
     defined = 0
+
     def link(self, rect, canvas):
         destinationname = self.url
         if not self.defined:
             [x, y, x1, y1] = rect
-            canvas.bookmarkHorizontal(destinationname, x,y1) # use the upper y
+            canvas.bookmarkHorizontal(destinationname, x, y1) # use the upper y
             self.defined = 1
 
 def splitspace(text):
     # split on spacing but include spaces at element ends
-    stext = string.split(text)
+    stext = text.split()
     result = []
     for e in stext:
         result.append(e+" ")

--AqsLC8rIMeq19msA--