reportlab-utf8 moved to trunk
authorrgbecker
Wed, 05 Apr 2006 15:18:32 +0000
changeset 2575 0cba68b93555
parent 2574 d81949596070
child 2576 1a563f1d80ec
reportlab-utf8 moved to trunk
reportlab/demos/stdfonts/stdfonts.py
reportlab/docs/userguide/ch1_intro.py
reportlab/docs/userguide/ch2a_fonts.py
reportlab/docs/userguide/ch5_paragraphs.py
reportlab/graphics/renderPDF.py
reportlab/graphics/renderPM.py
reportlab/graphics/shapes.py
reportlab/graphics/testshapes.py
reportlab/lib/rparsexml.py
reportlab/lib/styles.py
reportlab/lib/validators.py
reportlab/pdfbase/_cidfontdata.py
reportlab/pdfbase/cidfonts.py
reportlab/pdfbase/pdfdoc.py
reportlab/pdfbase/pdfmetrics.py
reportlab/pdfbase/ttfonts.py
reportlab/pdfgen/canvas.py
reportlab/pdfgen/textobject.py
reportlab/platypus/doctemplate.py
reportlab/platypus/flowables.py
reportlab/platypus/para.py
reportlab/platypus/paragraph.py
reportlab/platypus/paraparser.py
reportlab/platypus/tables.py
reportlab/rl_config.py
reportlab/test/test_graphics_charts.py
reportlab/test/test_multibyte_jpn.py
reportlab/test/test_pdfbase_encodings.py
reportlab/test/test_pdfbase_pdfmetrics.py
reportlab/test/test_pdfgen_general.py
reportlab/test/test_pdfgen_links.py
reportlab/test/test_platypus_tables.py
reportlab/test/test_tools_pythonpoint.py
reportlab/tools/docco/graphdocpy.py
reportlab/tools/docco/rl_doc_utils.py
reportlab/tools/pythonpoint/pythonpoint.py
reportlab/tools/pythonpoint/stdparser.py
--- a/reportlab/demos/stdfonts/stdfonts.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/demos/stdfonts/stdfonts.py	Wed Apr 05 15:18:32 2006 +0000
@@ -27,13 +27,12 @@
     for enc in ['MacRoman', 'WinAnsi']:
         canv = canvas.Canvas(
                 'StandardFonts_%s.pdf' % enc,
-                encoding=enc
                 )
         canv.setPageCompression(0)
 
         for faceName in pdfmetrics.standardFonts:
             if faceName in ['Symbol', 'ZapfDingbats']:
-                encLabel = 'StandardEncoding'
+                encLabel = faceName+'Encoding'
             else:
                 encLabel = enc + 'Encoding'
 
@@ -51,18 +50,14 @@
 
             #for dingbats, we need to use another font for the numbers.
             #do two parallel text objects.
-            if faceName == 'ZapfDingbats':
-                labelfont = 'Helvetica'
-            else:
-                labelfont = faceName
             for byt in range(32, 256):
                 col, row = divmod(byt - 32, 32)
                 x = 72 + (66*col)
                 y = 720 - (18*row)
-                canv.setFont(labelfont, 14)
+                canv.setFont('Helvetica', 14)
                 canv.drawString(x, y, label_formatter % byt)
                 canv.setFont(fontName, 14)
-                canv.drawString(x + 44, y , chr(byt))
+                canv.drawString(x+44, y, chr(byt).decode(encLabel,'ignore').encode('utf8'))
             canv.showPage()
         canv.save()
 
--- a/reportlab/docs/userguide/ch1_intro.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/docs/userguide/ch1_intro.py	Wed Apr 05 15:18:32 2006 +0000
@@ -239,7 +239,7 @@
 
 list("""To verify,
 start the Python interpreter (command line) and type $from PIL import Image$, followed by
-$import _imaging$ or (if you have a more modern copy of PIL) $import PIL._imaging$.  If you see no error messages, all is well.""")
+$import _imaging$.  If you see no error messages, all is well.""")
 
 disc("""Now you are ready to install reportlab itself.""")
 
--- a/reportlab/docs/userguide/ch2a_fonts.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/docs/userguide/ch2a_fonts.py	Wed Apr 05 15:18:32 2006 +0000
@@ -229,7 +229,7 @@
 pdfmetrics.registerFont(pdfmetrics.Font('MacHelvWithEuro', 'Helvetica-Oblique', 'MacWithEuro'))
 
 c.setFont('MacHelvWithEuro', 12)
-c.drawString(125, 575, 'Hacked MacRoman with Euro: Character 219 = "\333"') # oct(219)=0333
+c.drawString(125, 575, 'Hacked MacRoman with Euro: Character 219 = "\\xe2\\x82\\xac"') # utf8 for Euro
 """)
 
 heading2("Asian Font Support")
--- a/reportlab/docs/userguide/ch5_paragraphs.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/docs/userguide/ch5_paragraphs.py	Wed Apr 05 15:18:32 2006 +0000
@@ -288,8 +288,8 @@
 the paragraph, with its x origin determined by the $bulletIndent$
 attribute of the style, and in the font given in the
 $bulletFontName$ attribute.  For genuine bullets, a good
-idea is to select the Symbol font in the style, and
-use a character such as $\\267)$:""")
+idea is to select the Times-Roman font in the style, and
+use a character such as $\\xe2\\x80\\xa2)$:""")
 
 t=apply(Table,getAttrs(_bulletAttrMap))
 t.setStyle([
@@ -306,7 +306,7 @@
 overrides the implied bullet style and ^bulletText^ specified in the  ^Paragraph^
 creation.
 """)
-parabox("""<bullet>\267</bullet>this is a bullet point.  Spam
+parabox("""<bullet>\xe2\x80\xa2</bullet>this is a bullet point.  Spam
 spam spam spam spam spam spam spam spam spam spam spam
 spam spam spam spam spam spam spam spam spam spam """,
         styleSheet['Bullet'],
@@ -316,4 +316,4 @@
 except that a sequence tag is used.  It is also possible
 to put  a multi-character string in the bullet; with a deep
 indent and bold bullet font, you can make a compact
-definition list.""")
\ No newline at end of file
+definition list.""")
--- a/reportlab/graphics/renderPDF.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/graphics/renderPDF.py	Wed Apr 05 15:18:32 2006 +0000
@@ -148,10 +148,10 @@
     def drawString(self, stringObj):
         if self._fill:
             S = self._tracker.getState()
-            text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text
+            text_anchor, x, y, text, enc = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text, stringObj.encoding
             if not text_anchor in ['start','inherited']:
                 font, font_size = S['fontName'], S['fontSize']
-                textLen = stringWidth(text, font,font_size)
+                textLen = stringWidth(text, font, font_size, enc)
                 if text_anchor=='end':
                     x = x-textLen
                 elif text_anchor=='middle':
--- a/reportlab/graphics/renderPM.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/graphics/renderPM.py	Wed Apr 05 15:18:32 2006 +0000
@@ -10,7 +10,7 @@
 
 from reportlab.graphics.shapes import *
 from reportlab.graphics.renderbase import StateTracker, getStateDelta, renderScaledDrawing
-from reportlab.pdfbase.pdfmetrics import getFont
+from reportlab.pdfbase.pdfmetrics import getFont, unicode2T1
 from math import sin, cos, pi, ceil
 from reportlab.lib.utils import getStringIO, open_and_read
 from reportlab import rl_config
@@ -151,20 +151,51 @@
         self.drawPolyLine(polygon,_doClose=1)
 
     def drawString(self, stringObj):
-        fill = self._canvas.fillColor
+        canv = self._canvas
+        fill = canv.fillColor
         if fill is not None:
             S = self._tracker.getState()
-            text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text
+            text_anchor = S['textAnchor']
+            fontName = S['fontName']
+            fontSize = S['fontSize']
+            font = getFont(fontName)
+            text = stringObj.text
+            x = stringObj.x
+            y = stringObj.y
             if not text_anchor in ['start','inherited']:
-                font, font_size = S['fontName'], S['fontSize']
-                textLen = stringWidth(text, font,font_size)
+                textLen = stringWidth(text, fontName,fontSize)
                 if text_anchor=='end':
                     x = x-textLen
                 elif text_anchor=='middle':
                     x = x - textLen/2
                 else:
                     raise ValueError, 'bad value for textAnchor '+str(text_anchor)
-            self._canvas.drawString(x,y,text)
+            if getattr(font,'_dynamicFont',None):
+                if isinstance(text,unicode): text = text.encode('utf8')
+                canv.drawString(x,y,text)
+            else:
+                fc = font
+                if not isinstance(text,unicode):
+                    try:
+                        text = text.decode('utf8')
+                    except UnicodeDecodeError,e:
+                        i,j = e.args[2:4]
+                        raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],text[i-10:i],text[i:j],text[j:j+10]),)))
+
+                FT = unicode2T1(text,[font]+font.substitutionFonts)
+                n = len(FT)
+                nm1 = n-1
+                wscale = 0.001*fontSize
+                for i in xrange(n):
+                    f, t = FT[i]
+                    if f!=fc:
+                        canv.setFont(f.fontName,fontSize)
+                        fc = f
+                    canv.drawString(x,y,t)
+                    if i!=nm1:
+                        x += wscale*sum(map(f.widths.__getitem__,map(ord,t)))
+                if font!=fc:
+                    canv.setFont(fontName,fontSize)
 
     def drawPath(self, path):
         c = self._canvas
--- a/reportlab/graphics/shapes.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/graphics/shapes.py	Wed Apr 05 15:18:32 2006 +0000
@@ -1216,6 +1216,7 @@
         fontSize = AttrMapValue(isNumber),
         fillColor = AttrMapValue(isColorOrNone),
         textAnchor = AttrMapValue(isTextAnchor),
+        encoding = AttrMapValue(isString),
         )
 
     def __init__(self, x, y, text, **kw):
@@ -1227,9 +1228,10 @@
         self.fontSize = STATE_DEFAULTS['fontSize']
         self.fillColor = STATE_DEFAULTS['fillColor']
         self.setProperties(kw)
+        self.encoding = 'cp1252'  #matches only fonts we have!
 
     def getEast(self):
-        return self.x + stringWidth(self.text,self.fontName,self.fontSize)
+        return self.x + stringWidth(self.text,self.fontName,self.fontSize, self.encoding)
 
     def copy(self):
         new = self.__class__(self.x, self.y, self.text)
@@ -1238,7 +1240,7 @@
 
     def getBounds(self):
         # assumes constant drop of 0.2*size to baseline
-        w = stringWidth(self.text,self.fontName,self.fontSize)
+        w = stringWidth(self.text,self.fontName,self.fontSize, self.encoding)
         if self.textAnchor == 'start':
             x = self.x
         elif self.textAnchor == 'middle':
--- a/reportlab/graphics/testshapes.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/graphics/testshapes.py	Wed Apr 05 15:18:32 2006 +0000
@@ -74,6 +74,7 @@
     D = Drawing(400, 200)
     D.add(Rect(50, 50, 300, 100, fillColor=colors.yellow))
     D.add(String(180,100, 'Hello World', fillColor=colors.red))
+    D.add(String(180,86, 'Some special characters \xc2\xa2\xc2\xa9\xc2\xae\xc2\xa3\xca\xa5\xd0\x96\xd6\x83\xd7\x90\xd9\x82\xe0\xa6\x95\xce\xb1\xce\xb2\xce\xb3', fillColor=colors.red))
 
     return D
 
@@ -399,9 +400,10 @@
     pdfmetrics.registerFont(ttfonts.TTFont("Rina", "rina.ttf"))
     _FONTS[1] = 'LuxiSerif'
     _FONTS[2] = 'Rina'
-    F = ['Times-Roman','LuxiSerif', 'Rina']
+    F = ['Times-Roman','Courier','Helvetica','LuxiSerif', 'Rina']
     if sys.platform=='win32':
         for name, ttf in [('Adventurer Light SF','Advlit.ttf'),('ArialMS','ARIAL.TTF'),
+            ('Arial Unicode MS', 'ARIALUNI.TTF'),
             ('Book Antiqua','BKANT.TTF'),
             ('Century Gothic','GOTHIC.TTF'),
             ('Comic Sans MS', 'COMIC.TTF'),
@@ -432,7 +434,7 @@
         maxx = 0
         for fontName in F:
             y -= th
-            text = fontName+": I should be totally horizontal and enclosed in a box"
+            text = fontName+": I should be totally horizontal and enclosed in a box and end in alphabetagamma \xc2\xa2\xc2\xa9\xc2\xae\xc2\xa3\xca\xa5\xd0\x96\xd6\x83\xd7\x90\xd9\x82\xe0\xa6\x95\xce\xb1\xce\xb2\xce\xb3"
             textWidth = stringWidth(text, fontName, fontSize)
             maxx = max(maxx,textWidth+20)
             D.add(
@@ -468,7 +470,6 @@
 ##
 ##
 ##    return D
-
 def getAllFunctionDrawingNames(doTTF=1):
     "Get a list of drawing function names from somewhere."
 
@@ -485,10 +486,10 @@
     return funcNames
 
 def _evalFuncDrawing(name, D, l=None, g=None):
-    #try:
-    d = eval(name + '()', g or globals(), l or locals())
-    #except:
-    #   d = getFailedDrawing(name)
+    try:
+        d = eval(name + '()', g or globals(), l or locals())
+    except:
+        d = getFailedDrawing(name)
     D.append((d, eval(name + '.__doc__'), name[3:]))
 
 def getAllTestDrawings(doTTF=1):
--- a/reportlab/lib/rparsexml.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/lib/rparsexml.py	Wed Apr 05 15:18:32 2006 +0000
@@ -48,31 +48,22 @@
 try:
     #raise ImportError, "dummy error"
     simpleparse = 0
-    import pyRXP
-    if pyRXP.version>='0.5':
-        def warnCB(s):
-            print s
-        pyRXP_parser = pyRXP.Parser(
-                            ErrorOnValidityErrors=1,
-                            NoNoDTDWarning=1,
-                            ExpandCharacterEntities=0,
-                            ExpandGeneralEntities=0,
-                            warnCB = warnCB,
-                            srcName='string input')
-        def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None):
-            pyRXP_parser.eoCB = eoCB
-            p = pyRXP_parser.parse(xmlText)
-            return oneOutermostTag and p or ('',None,[p],None)
-    else:
-        def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None):
-            '''eoCB is the entity open callback'''
-            def warnCB(s):
-                print s
-            flags = 0x0157e1ff | pyRXP.PARSER_FLAGS['ErrorOnValidityErrors']
-            for k in ('ExpandCharacterEntities','ExpandGeneralEntities'):
-                flags = flags & (~pyRXP.PARSER_FLAGS[k])
-            p = pyRXP.parse(xmlText,srcName='string input',flags=flags,warnCB=warnCB,eoCB=eoCB)
-            return oneOutermostTag and p or ('',None,[p],None)
+    import pyRXPU
+    def warnCB(s):
+        print s
+    pyRXP_parser = pyRXPU.Parser(
+                        ErrorOnValidityErrors=1,
+                        NoNoDTDWarning=1,
+                        ExpandCharacterEntities=1,
+                        ExpandGeneralEntities=1,
+                        warnCB = warnCB,
+                        srcName='string input',
+                        ReturnUTF8 = 1,
+                        )
+    def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None):
+        pyRXP_parser.eoCB = eoCB
+        p = pyRXP_parser.parse(xmlText)
+        return oneOutermostTag and p or ('',None,[p],None)
 except ImportError:
     simpleparse = 1
 
--- a/reportlab/lib/styles.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/lib/styles.py	Wed Apr 05 15:18:32 2006 +0000
@@ -79,7 +79,8 @@
         'bulletFontSize':10,
         'bulletIndent':0,
         'textColor': black,
-        'backColor':None
+        'backColor':None,
+        'wordWrap':None,
         }
 
 class LineStyle(PropertySet):
--- a/reportlab/lib/validators.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/lib/validators.py	Wed Apr 05 15:18:32 2006 +0000
@@ -7,7 +7,7 @@
 used in an attribute map.
 """
 
-import string, sys,re
+import string, sys
 from types import *
 _SequenceTypes = (ListType,TupleType)
 _NumberTypes = (FloatType,IntType)
@@ -64,7 +64,7 @@
 
 class _isString(Validator):
     def test(self,x):
-        return type(x) is StringType
+        return type(x) in (StringType, UnicodeType)
 
 class _isNumber(Validator):
     def test(self,x):
@@ -156,7 +156,7 @@
 
 class _isValidChildOrNone(_isValidChild):
     def test(self,x):
-        return x is None or _isValidChild.test(self,x)
+        return _isValidChild.test(self,x) or x is None
 
 class _isCallable(Validator):
     def test(self, x):
@@ -252,7 +252,6 @@
             text = str(x)
         return (self._pattern.match(text) <> None)
 
-
 class DerivedValue:
     """This is used for magic values which work themselves out.
     An example would be an "inherit" property, so that one can have
@@ -271,7 +270,6 @@
         a correct stack of parent nodes."""
         return None
 
-
 class Inherit(DerivedValue):
     def __repr__(self):
         return "inherit"
--- a/reportlab/pdfbase/_cidfontdata.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfbase/_cidfontdata.py	Wed Apr 05 15:18:32 2006 +0000
@@ -126,6 +126,28 @@
                     encodings_kor
                     )
 
+defaultUnicodeEncodings = {
+    #we ddefine a default Unicode encoding for each face name;
+    #this should be the most commonly used horizontal unicode encoding;
+    #also define a 3-letter language code.
+    'HeiseiMin-W3': ('jpn','UniJIS-UCS2-H'),
+    'HeiseiKakuGo-W5': ('jpn','UniJIS-UCS2-H'),
+    'STSong-Light': ('chs', 'UniGB-UCS2-H'),
+    'MSung-Light': ('cht', 'UniGB-UCS2-H'),
+    'MHei-Medium': ('cht', 'UniGB-UCS2-H'),
+    'HYSMyeongJo-Medium': ('kor', 'UniKS-UCS2-H'),
+    'HYGothic-Medium': ('kor','UniKS-UCS2-H'),
+    }
+
+typeFaces_chs = ['STSong-Light'] # to do
+typeFaces_cht = ['MSung-Light', 'MHei-Medium'] # to do
+typeFaces_jpn = ['HeiseiMin-W3', 'HeiseiKakuGo-W5']
+typeFaces_kor = ['HYSMyeongJo-Medium','HYGothic-Medium']
+
+
+#declare separately those used for unicode
+unicode_encodings = [enc for enc in allowedEncodings if 'UCS2' in enc]
+
 
 CIDFontInfo = {}
 #statically describe the fonts in Adobe's Japanese Language Packs
@@ -449,4 +471,4 @@
 ##            out.append(word)
 ##        else:
 ##            out.append(word + ',')
-##    return eval(string.join(out, ''))
\ No newline at end of file
+##    return eval(string.join(out, ''))
--- a/reportlab/pdfbase/cidfonts.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfbase/cidfonts.py	Wed Apr 05 15:18:32 2006 +0000
@@ -17,9 +17,10 @@
 
 import reportlab
 from reportlab.pdfbase import pdfmetrics
-from reportlab.pdfbase._cidfontdata import allowedTypeFaces, allowedEncodings, CIDFontInfo
+from reportlab.pdfbase._cidfontdata import allowedTypeFaces, allowedEncodings, CIDFontInfo, defaultUnicodeEncodings
 from reportlab.pdfgen.canvas import Canvas
 from reportlab.pdfbase import pdfdoc
+from reportlab.pdfbase.pdfutils import _escape
 from reportlab.rl_config import CMapSearchPath
 
 
@@ -28,6 +29,7 @@
     for dirname in CMapSearchPath:
         cmapfile = dirname + os.sep + name
         if os.path.isfile(cmapfile):
+            #print "found", cmapfile
             return cmapfile
     raise IOError, 'CMAP file for encodings "%s" not found!' % name
 
@@ -204,6 +206,16 @@
         finished = time.clock()
         #print 'loaded %s in %0.4f seconds' % (self.name, finished - started)
 
+    def getData(self):
+        """Simple persistence helper.  Return a dict with all that matters."""
+        return {
+            'mapFileHash': self._mapFileHash,
+            'codeSpaceRanges': self._codeSpaceRanges,
+            'notDefRanges': self._notDefRanges,
+            'cmap': self._cmap,
+            
+            }
+    
 
 class CIDTypeFace(pdfmetrics.TypeFace):
     """Multi-byte type face.
@@ -292,7 +304,16 @@
         self.isVertical = (self.encodingName[-1] == 'V')
 
 
-    def stringWidth(self, text, size):
+        #no substitutes initially
+        self.substitutionFonts = []
+        
+    def formatForPdf(self, text):
+        encoded = _escape(text)
+        #print 'encoded CIDFont:', encoded
+        return encoded
+
+    def stringWidth(self, text, size, encoding=None):
+        """This presumes non-Unicode input.  UnicodeCIDFont wraps it for that context"""
         cidlist = self.encoding.translate(text)
         if self.isVertical:
             #this part is "not checked!" but seems to work.
@@ -325,6 +346,88 @@
         doc.fontMapping[self.name] = '/' + internalName
 
 
+class UnicodeCIDFont(CIDFont):
+    """Wraps up CIDFont to hide explicit encoding choice;
+    encodes text for output as UTF16.
+
+    lang should be one of 'jpn',chs','cht','kor' for now.
+    if vertical is set, it will select a different widths array
+    and possibly glyphs for some punctuation marks.
+
+    halfWidth is only for Japanese.
+
+
+    >>> dodgy = UnicodeCIDFont('nonexistent')
+    Traceback (most recent call last):
+    ...
+    KeyError: "don't know anything about CID font nonexistent"
+    >>> heisei = UnicodeCIDFont('HeiseiMin-W3')
+    >>> heisei.name
+    'HeiseiMin-W3'
+    >>> heisei.language
+    'jpn'
+    >>> heisei.encoding.name
+    'UniJIS-UCS2-H'
+    >>> #This is how PDF data gets encoded.
+    >>> print heisei.formatForPdf('hello')
+    \\377\\376h\\000e\\000l\\000l\\000o\\000
+    >>> tokyo = u'\u6771\u4AEC'
+    >>> print heisei.formatForPdf(tokyo)
+    \\377\\376qg\\354J
+    
+    """
+
+    def __init__(self, face, isVertical=False, isHalfWidth=False):
+        #pass
+        try:
+            lang, defaultEncoding = defaultUnicodeEncodings[face]
+        except KeyError:
+            raise KeyError("don't know anything about CID font %s" % face)
+
+        #we know the languages now.
+        self.language = lang
+        
+        #rebuilt encoding string.  They follow rules which work
+        #for the 7 fonts provided.
+        enc = defaultEncoding[:-1]
+        if isHalfWidth:
+            enc = enc + 'HW-'
+        if isVertical:
+            enc = enc + 'V'
+        else:
+            enc = enc + 'H'
+            
+        #now we can do the more general case               
+        CIDFont.__init__(self, face, enc)
+        #self.encName = 'utf_16_le'
+        #it's simpler for unicode, just use the face name
+        self.name = self.fontName = face   
+        self.vertical = isVertical
+        self.isHalfWidth = isHalfWidth
+
+
+    def formatForPdf(self, text):
+        #these ones should be encoded asUTF16 minus the BOM
+        from codecs import utf_16_be_encode
+        #print 'formatting %s: %s' % (type(text), repr(text))
+        if type(text) is not unicode:
+            text = text.decode('utf8')
+        utfText = utf_16_be_encode(text)[0]
+        encoded = _escape(utfText)
+        #print '  encoded:',encoded
+        return encoded
+        #
+        #result = _escape(encoded)
+        #print '    -> %s' % repr(result)
+        #return result
+    
+
+    def stringWidth(self, text, size, encoding=None):
+        "Just ensure we do width test on characters, not bytes..."
+        if type(text) is type(''):
+            text = text.decode('utf8')
+        return CIDFont.stringWidth(self, text, size, encoding)
+            
 
 def precalculate(cmapdir):
     # crunches through all, making 'fastmap' files
@@ -392,7 +495,9 @@
 ##    print 'constructed all encodings in %0.2f seconds' % (finished - started)
 
 if __name__=='__main__':
-    test()
+    import doctest, cidfonts
+    doctest.testmod(cidfonts)
+    #test()
 
 
 
--- a/reportlab/pdfbase/pdfdoc.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfbase/pdfdoc.py	Wed Apr 05 15:18:32 2006 +0000
@@ -15,7 +15,7 @@
 classes are made available elsewhere for users to manipulate.
 """
 
-import string, types, binascii
+import string, types, binascii, codecs
 from reportlab.pdfbase import pdfutils
 from reportlab.pdfbase.pdfutils import LINEEND # this constant needed in both
 from reportlab import rl_config
@@ -136,19 +136,10 @@
     encrypt = NoEncryption() # default no encryption
     pageCounter = 1
     def __init__(self,
-                 encoding=rl_config.defaultEncoding,
                  dummyoutline=0,
                  compression=rl_config.pageCompression,
                  invariant=rl_config.invariant,
                  filename=None):
-        #self.defaultStreamFilters = [PDFBase85Encode, PDFZCompress] # for testing!
-        #self.defaultStreamFilters = [PDFZCompress] # for testing!
-        assert encoding in ['MacRomanEncoding',
-                            'WinAnsiEncoding',
-                            'MacRoman',
-                            'WinAnsi'], 'Unsupported encoding %s' % encoding
-        if encoding[-8:] <> 'Encoding':
-            encoding = encoding + 'Encoding'
 
         # allow None value to be passed in to mean 'give system defaults'
         if invariant is None:
@@ -156,7 +147,6 @@
         else:
             self.invariant = invariant
         self.setCompression(compression)
-        self.encoding = encoding
         # signature for creating PDF ID
         import md5
         sig = self.signature = md5.new()
@@ -334,8 +324,6 @@
         fontnames.sort()
         return fontnames
 
-
-
     def format(self):
         # register the Catalog/INfo and then format the objects one by one until exhausted
         # (possible infinite loop if there is a bug that continually makes new objects/refs...)
@@ -361,7 +349,7 @@
         done = None
         File = PDFFile() # output collector
         while done is None:
-            counter = counter+1 # do next object...
+            counter += 1 # do next object...
             if numbertoid.has_key(counter):
                 id = numbertoid[counter]
                 #printidToOb
@@ -1082,7 +1070,7 @@
         self.buildtree = []
         self.closedict = {} # dictionary of "closed" destinations in the outline
 
-    def addOutlineEntry(self, destinationname, level=0, title=None, closed=None):
+    def addOutlineEntry(self, destinationname, level=0, title=None, closed=None, asUtf16=False):
         """destinationname of None means "close the tree" """
         from types import IntType, TupleType
         if destinationname is None and level!=0:
@@ -1116,6 +1104,12 @@
             currentlevel = currentlevel-1
         if destinationname is None: return
         stack[-1].append(destinationname)
+        if asUtf16:
+            if type(title) is str:
+                title = title.decode('utf8')
+            elif type(title) is not unicode:
+                raise ValueError('title must be utf8 string or unicode')
+            title = codecs.BOM_UTF16_BE+title.encode('utf_16_be')
         self.destinationnamestotitles[destinationname] = title
         if closed: self.closedict[destinationname] = 1
         self.currentlevel = level
--- a/reportlab/pdfbase/pdfmetrics.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfbase/pdfmetrics.py	Wed Apr 05 15:18:32 2006 +0000
@@ -23,28 +23,58 @@
 from types import StringType, ListType, TupleType
 from reportlab.pdfbase import _fontdata
 from reportlab.lib.logger import warnOnce
-from reportlab.lib.utils import rl_isfile, rl_isdir, open_and_read, open_and_readlines, rl_glob
+from reportlab.lib.utils import rl_isfile, rl_glob, rl_isdir, open_and_read, open_and_readlines 
 from reportlab.rl_config import defaultEncoding
+import rl_codecs
 
+rl_codecs.RL_Codecs.register()
 standardFonts = _fontdata.standardFonts
 standardEncodings = _fontdata.standardEncodings
 
-_dummyEncoding=' _not an encoding_ '
-# conditional import - try both import techniques, and set a flag
+# AR 20040612 - disabling accelerated stringwidth until I have
+# a slow one which works right for Unicode.  Then we can change
+# the accelerated one.
+##_dummyEncoding=' _not an encoding_ '
+## conditional import - try both import techniques, and set a flag
 try:
-    import _rl_accel
-    try:
-        _stringWidth = _rl_accel.stringWidth
-        _rl_accel.defaultEncoding(_dummyEncoding)
-    except:
-        _stringWidth = None
+      import _rl_accel
+      try:
+          _stringWidth = _rl_accel.stringWidth
+          #_rl_accel.defaultEncoding(_dummyEncoding)
+      except:
+          _stringWidth = None
 except ImportError:
-    _stringWidth = None
+      _stringWidth = None
+_stringWidth = None
 
 _typefaces = {}
 _encodings = {}
 _fonts = {}
 
+def unicode2T1(utext,fonts):
+    '''return a list of (font,string) pairs representing the unicode text'''
+    #print 'unicode2t1(%s, %s): %s' % (utext, fonts, type(utext))
+    #if type(utext) 
+    R = []
+    font, fonts = fonts[0], fonts[1:]
+    enc = font.encName
+    if 'UCS-2' in enc:
+        enc = 'UTF16'
+    while utext:
+        try:
+            R.append((font,utext.encode(enc)))
+            break
+        except UnicodeEncodeError, e:
+            i0, il = e.args[2:4]
+            if i0:
+                R.append((font,utext[:i0].encode(enc)))
+            if fonts:
+                R.extend(unicode2T1(utext[i0:il],fonts))
+            else:
+                R.append((_notdefFont,_notdefChar*(il-i0)))
+            utext = utext[il:]
+    return R
+
 class FontError(Exception):
     pass
 class FontNotFoundError(Exception):
@@ -326,6 +356,7 @@
 #for encName in standardEncodings:
 #    registerEncoding(Encoding(encName))
 
+standardT1SubstitutionFonts = []
 class Font:
     """Represents a font (i.e combination of face and encoding).
 
@@ -336,13 +367,23 @@
     composition)"""
     def __init__(self, name, faceName, encName):
         self.fontName = name
-        self.face = getTypeFace(faceName)
+        face = self.face = getTypeFace(faceName)
         self.encoding= getEncoding(encName)
+        self.encName = encName
+        if face.builtIn and face.requiredEncoding is None:
+            _ = standardT1SubstitutionFonts
+        else:
+            _ = []
+        self.substitutionFonts = _
         self._calcWidths()
 
         # multi byte fonts do their own stringwidth calculations.
         # signal this here.
         self._multiByte = 0
+        
+
+    def __repr__(self):
+        return "<%s %s>" % (self.__class__.__name__, self.face.name)
 
     def _calcWidths(self):
         """Vector of widths for stringWidth function"""
@@ -365,15 +406,12 @@
         self.widths = w
 
     if not _stringWidth:
-        def stringWidth(self, text, size):
-            """This is the "purist" approach to width.  The practical one
-            is to use the stringWidth one which may be optimized
-            in C."""
-            w = 0
-            widths = self.widths
-            for ch in text:
-                w = w + widths[ord(ch)]
-            return w * 0.001 * size
+        def stringWidth(self, text, size, encoding='utf8'):
+            """This is the "purist" approach to width.  The practical approach
+            is to use the stringWidth function, which may be swapped in for one
+            written in C."""
+            if not isinstance(text,unicode): text = text.decode(encoding)
+            return sum([sum(map(f.widths.__getitem__,map(ord,t))) for f, t in unicode2T1(text,[self]+self.substitutionFonts)])*0.001*size
 
     def _formatWidths(self):
         "returns a pretty block in PDF Array format to aid inspection"
@@ -653,6 +691,8 @@
             font = Font(fontName, fontName, defaultEncoding)
         registerFont(font)
         return font
+_notdefFont,_notdefChar = getFont('ZapfDingbats'),chr(110)
+standardT1SubstitutionFonts.extend([getFont('Symbol'),getFont('ZapfDingbats')])
 
 def getAscentDescent(fontName):
     font = getFont(fontName)
@@ -673,17 +713,9 @@
     reg.sort()
     return reg
 
-def _slowStringWidth(text, fontName, fontSize):
+def _slowStringWidth(text, fontName, fontSize, encoding='utf8'):
     """Define this anyway so it can be tested, but whether it is used or not depends on _rl_accel"""
-    font = getFont(fontName)
-    return font.stringWidth(text, fontSize)
-    #this is faster, but will need more special-casing for multi-byte fonts.
-    #wid = getFont(fontName).widths
-    #w = 0
-    #for ch in text:
-    #    w = w + wid[ord(ch)]
-    #return 0.001 * w * fontSize
-
+    return getFont(fontName).stringWidth(text, fontSize, encoding=encoding)
 
 if _stringWidth:
     import new
@@ -744,11 +776,11 @@
     # checks all 3 algorithms give same answer, note speed
     import time
     for fontName in standardFonts[0:1]:
-        t0 = time.time()
-        for text in texts:
-            l1 = _stringWidth(text, fontName, 10)
-        t1 = time.time()
-        print 'fast stringWidth took %0.4f' % (t1 - t0)
+##        t0 = time.time()
+##        for text in texts:
+##            l1 = stringWidth(text, fontName, 10)
+##        t1 = time.time()
+##        print 'fast stringWidth took %0.4f' % (t1 - t0)
 
         t0 = time.time()
         w = getFont(fontName).widths
@@ -788,7 +820,6 @@
 
     dumpFontData()
 
-
 if __name__=='__main__':
     test()
     testStringWidthAlgorithms()
--- a/reportlab/pdfbase/ttfonts.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfbase/ttfonts.py	Wed Apr 05 15:18:32 2006 +0000
@@ -60,7 +60,7 @@
 __version__ = '$Id$'
 
 import string
-from types import StringType
+from types import StringType, UnicodeType
 from struct import pack, unpack
 from cStringIO import StringIO
 from reportlab.pdfbase import pdfmetrics, pdfdoc
@@ -105,7 +105,7 @@
         "endcodespacerange",
         "%d beginbfchar" % len(subset)
     ] + map(lambda n, subset=subset: "<%02X> <%04X>" % (n, subset[n]),
-            range(len(subset))) + [
+            xrange(len(subset))) + [
         "endbfchar",
         "endcmap",
         "CMapName currentdict /CMap defineresource pop",
@@ -237,7 +237,7 @@
             # Read table directory
             self.table = {}
             self.tables = []
-            for n in range(self.numTables):
+            for n in xrange(self.numTables):
                 record = {}
                 record['tag'] = self.read_tag()
                 record['checksum'] = self.read_ulong()
@@ -428,7 +428,7 @@
         names = {1:None,2:None,3:None,4:None,6:None}
         K = names.keys()
         nameCount = len(names)
-        for i in range(numRecords):
+        for i in xrange(numRecords):
             platformId = self.read_ushort()
             encodingId = self.read_ushort()
             languageId = self.read_ushort()
@@ -591,7 +591,7 @@
         self.skip(2)
         cmapTableCount = self.read_ushort()
         unicode_cmap_offset = None
-        for n in range(cmapTableCount):
+        for n in xrange(cmapTableCount):
             platformID = self.read_ushort()
             encodingID = self.read_ushort()
             offset = self.read_ulong()
@@ -613,18 +613,18 @@
         self.skip(2)
         segCount = self.read_ushort() / 2
         self.skip(6)
-        endCount = map(lambda x, self=self: self.read_ushort(), range(segCount))
+        endCount = map(lambda x, self=self: self.read_ushort(), xrange(segCount))
         self.skip(2)
-        startCount = map(lambda x, self=self: self.read_ushort(), range(segCount))
-        idDelta = map(lambda x, self=self: self.read_short(), range(segCount))
+        startCount = map(lambda x, self=self: self.read_ushort(), xrange(segCount))
+        idDelta = map(lambda x, self=self: self.read_short(), xrange(segCount))
         idRangeOffset_start = self._pos
-        idRangeOffset = map(lambda x, self=self: self.read_ushort(), range(segCount))
+        idRangeOffset = map(lambda x, self=self: self.read_ushort(), xrange(segCount))
 
         # Now it gets tricky.
         glyphToChar = {}
         charToGlyph = {}
-        for n in range(segCount):
-            for unichar in range(startCount[n], endCount[n] + 1):
+        for n in xrange(segCount):
+            for unichar in xrange(startCount[n], endCount[n] + 1):
                 if idRangeOffset[n] == 0:
                     glyph = (unichar + idDelta[n]) & 0xFFFF
                 else:
@@ -650,7 +650,7 @@
         aw = None
         self.charWidths = {}
         self.hmetrics = []
-        for glyph in range(numberOfHMetrics):
+        for glyph in xrange(numberOfHMetrics):
             # advance width and left side bearing.  lsb is actually signed
             # short, but we don't need it anyway (except for subsetting)
             aw, lsb = self.read_ushort(), self.read_ushort()
@@ -661,7 +661,7 @@
             if glyphToChar.has_key(glyph):
                 for char in glyphToChar[glyph]:
                     self.charWidths[char] = aw
-        for glyph in range(numberOfHMetrics, numGlyphs):
+        for glyph in xrange(numberOfHMetrics, numGlyphs):
             # the rest of the table only lists advance left side bearings.
             # so we reuse aw set by the last iteration of the previous loop
             lsb = self.read_ushort()
@@ -674,10 +674,10 @@
         self.seek_table('loca')
         self.glyphPos = []
         if indexToLocFormat == 0:
-            for n in range(numGlyphs + 1):
+            for n in xrange(numGlyphs + 1):
                 self.glyphPos.append(self.read_ushort() << 1)
         elif indexToLocFormat == 1:
-            for n in range(numGlyphs + 1):
+            for n in xrange(numGlyphs + 1):
                 self.glyphPos.append(self.read_ulong())
         else:
             raise TTFError, 'Unknown location table format (%d)' % indexToLocFormat
@@ -781,7 +781,7 @@
 
         # hmtx - Horizontal Metrics
         hmtx = []
-        for n in range(numGlyphs):
+        for n in xrange(numGlyphs):
             originalGlyphIdx = glyphMap[n]
             aw, lsb = self.hmetrics[originalGlyphIdx]
             if n < numberOfHMetrics:
@@ -795,7 +795,7 @@
         offsets = []
         glyf = []
         pos = 0
-        for n in range(numGlyphs):
+        for n in xrange(numGlyphs):
             offsets.append(pos)
             originalGlyphIdx = glyphMap[n]
             glyphPos = self.glyphPos[originalGlyphIdx]
@@ -967,15 +967,14 @@
         self._dynamicFont = 1   # We want dynamic subsetting
         self.state = {}
 
-    def stringWidth(self, text, size):
+    def stringWidth(self, text, size, encoding='utf-8'):
         "Calculate text width"
+        if type(text) is not UnicodeType:
+            text = unicode(text, encoding or 'utf-8')   # encoding defaults to utf-8
         width = self.face.getCharWidth
-        w = 0
-        for code in parse_utf8(text):
-            w = w + width(code)
-        return 0.001 * w * size
+        return 0.001*size*sum([width(ord(u)) for u in text])
 
-    def splitString(self, text, doc):
+    def splitString(self, text, doc, encoding='utf-8'):
         """Splits text into a number of chunks, each of which belongs to a
         single subset.  Returns a list of tuples (subset, string).  Use subset
         numbers with getSubsetInternalName.  Doc is needed for distinguishing
@@ -985,7 +984,9 @@
         curSet = -1
         cur = []
         results = []
-        for code in parse_utf8(text):
+        if type(text) is not UnicodeType:
+            text = unicode(text, encoding or 'utf-8')   # encoding defaults to utf-8
+        for code in map(ord,text):
             if state.assignments.has_key(code):
                 n = state.assignments[code]
             else:
@@ -1037,7 +1038,7 @@
         try: state = self.state[doc]
         except KeyError: state = self.state[doc] = TTFont.State()
         state.frozen = 1
-        for n in range(len(state.subsets)):
+        for n in xrange(len(state.subsets)):
             subset = state.subsets[n]
             internalName = self.getSubsetInternalName(n, doc)[1:]
             baseFontName = "%s+%s" % (SUBSETN(n),self.face.name)
--- a/reportlab/pdfgen/canvas.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfgen/canvas.py	Wed Apr 05 15:18:32 2006 +0000
@@ -63,6 +63,17 @@
     def _digester(s):
         return join(map(lambda x : "%02x" % ord(x), md5.md5(s).digest()), '')
 
+def _annFormat(D,color,thickness,dashArray):
+    from reportlab.pdfbase.pdfdoc import PDFArray
+    if color:
+        D["C"] = PDFArray([color.red, color.green, color.blue])
+    border = [0,0,0]
+    if thickness:
+        border[2] = thickness
+    if dashArray:
+        border.append(PDFArray(dashArray))
+    D["Border"] = PDFArray(border)
+
 class Canvas(textobject._PDFColorSetter):
     """This class is the programmer's interface to the PDF file format.  Methods
     are (or will be) provided here to do just about everything PDF can do.
@@ -114,7 +125,6 @@
                  pagesize=None,
                  bottomup = 1,
                  pageCompression=None,
-                 encoding = None,
                  invariant = None,
                  verbosity=0):
         """Create a canvas of a given size. etc.
@@ -125,12 +135,10 @@
         Most of the attributes are private - we will use set/get methods
         as the preferred interface.  Default page size is A4."""
         if pagesize is None: pagesize = rl_config.defaultPageSize
-        if encoding is None: encoding = rl_config.defaultEncoding
         if invariant is None: invariant = rl_config.invariant
         self._filename = filename
-        self._encodingName = encoding
-        self._doc = pdfdoc.PDFDocument(encoding,
-                                       compression=pageCompression,
+
+        self._doc = pdfdoc.PDFDocument(compression=pageCompression,
                                        invariant=invariant, filename=filename)
 
 
@@ -176,6 +184,7 @@
         self._y = 0
         self._fontname = 'Times-Roman'
         self._fontsize = 12
+
         self._dynamicFont = 0
         self._textMode = 0  #track if between BT/ET
         self._leading = 14.4
@@ -244,7 +253,7 @@
            not automatically be seen when the document is viewed."""
         self._doc.setAuthor(author)
 
-    def addOutlineEntry(self, title, key, level=0, closed=None):
+    def addOutlineEntry(self, title, key, level=0, closed=None, asUtf16=False):
         """Adds a new entry to the outline at given level.  If LEVEL not specified,
         entry goes at the top level.  If level specified, it must be
         no more than 1 greater than the outline level in the last call.
@@ -294,7 +303,7 @@
         """
         #to be completed
         #self._outlines.append(title)
-        self._doc.outline.addOutlineEntry(key, level, title, closed=closed)
+        self._doc.outline.addOutlineEntry(key, level, title, closed=closed, asUtf16=asUtf16)
 
     def setOutlineNames0(self, *nametree):   # keep this for now (?)
         """nametree should can be a recursive tree like so
@@ -401,7 +410,7 @@
         return result
 
     def bookmarkPage(self, key,
-                      fitType="Fit",
+                      fit="Fit",
                       left=None,
                       top=None,
                       bottom=None,
@@ -453,30 +462,30 @@
         if zoom is None:
             zoom = "null"
 
-        if fitType == "XYZ":
+        if fit == "XYZ":
             dest.xyz(left,top,zoom)
-        elif fitType == "Fit":
+        elif fit == "Fit":
             dest.fit()
-        elif fitType == "FitH":
+        elif fit == "FitH":
             dest.fith(top)
-        elif fitType == "FitV":
+        elif fit == "FitV":
             dest.fitv(left)
-        elif fitType == "FitR":
+        elif fit == "FitR":
             dest.fitr(left,bottom,right,top)
         #Do we need these (version 1.1 / Acrobat 3 versions)?
-        elif fitType == "FitB":
+        elif fit == "FitB":
             dest.fitb()
-        elif fitType == "FitBH":
+        elif fit == "FitBH":
             dest.fitbh(top)
-        elif fitType == "FitBV":
+        elif fit == "FitBV":
             dest.fitbv(left)
         else:
-            raise "Unknown Fit type %s" % (fitType,)
+            raise "Unknown Fit type %s" % (fit,)
 
         dest.setPage(pageref)
         return dest
 
-    def bookmarkHorizontalAbsolute(self, key, yhorizontal):
+    def bookmarkHorizontalAbsolute(self, key, top, left=0, fit='XYZ', **kw):
         """Bind a bookmark (destination) to the current page at a horizontal position.
            Note that the yhorizontal of the book mark is with respect to the default
            user space (where the origin is at the lower left corner of the page)
@@ -485,12 +494,12 @@
            responsible for making sure the bookmark matches an appropriate item on
            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)
+        return self.bookmarkPage(key, fit=fit, top=top, left=left, zoom=0)
 
-    def bookmarkHorizontal(self, key, relativeX, relativeY):
+    def bookmarkHorizontal(self, key, relativeX, relativeY, **kw):
         """w.r.t. the current transformation, bookmark this horizontal."""
-        (xt, yt) = self.absolutePosition(relativeX,relativeY)
-        self.bookmarkHorizontalAbsolute(key, yt)
+        (left, top) = self.absolutePosition(relativeX,relativeY)
+        self.bookmarkHorizontalAbsolute(key, top, left=left, **kw)
 
     #def _inPage0(self):  disallowed!
     #    """declare a page, enable page features"""
@@ -527,9 +536,11 @@
         height are omitted, they are calculated from the image size.
         Also allow file names as well as images.  The size in pixels
         of the image is returned."""
+
+        self._currentPageHasImages = 1
         from pdfimages import PDFImage
         img_obj = PDFImage(image, x,y, width, height)
-        if img_obj.drawInlineImage(self): self._currentPageHasImages = 1
+        img_obj.drawInlineImage(self)
         return (img_obj.width, img_obj.height)
 
     def drawImage(self, image, x, y, width=None, height=None, mask=None):
@@ -557,6 +568,8 @@
 
         In general you should use drawImage in preference to drawInlineImage
         unless you have read the PDF Spec and understand the tradeoffs."""
+        self._currentPageHasImages = 1
+
         # first, generate a unique name/signature for the image.  If ANYTHING
         # is different, even the mask, this should be different.
         if type(image) == type(''):
@@ -583,9 +596,6 @@
             width = imgObj.width
         if height is None:
             height = imgObj.height
-        if width<1e-6 or height<1e-6: return
-    
-        self._currentPageHasImages = 1
 
         # scale and draw
         self.saveState()
@@ -737,7 +747,8 @@
         self._addAnnotation(pdfdoc.InkAnnotation(Rect, contents, InkList, **kw), name, addtopage)
     inkAnnotation0 = inkAnnotation  #deprecated
 
-    def linkAbsolute(self, contents, destinationname, Rect=None, addtopage=1, name=None, **kw):
+    def linkAbsolute(self, contents, destinationname, Rect=None, addtopage=1, name=None,
+            thickness=0, color=None, dashArray=None, **kw):
         """rectangular link annotation positioned wrt the default user space.
            The identified rectangle on the page becomes a "hot link" which
            when clicked will send the viewer to the page and position identified
@@ -752,18 +763,21 @@
 
            You may want to use the keyword argument Border='[0 0 0]' to
            suppress the visible rectangle around the during viewing link."""
+        return self.linkRect(contents, destinationname, Rect, addtopage, name, relative=0,
+                thickness=thickness, color=color, dashArray=dashArray, **kw)
+
+    def linkRect(self, contents, destinationname, Rect=None, addtopage=1, name=None, relative=0,
+            thickness=0, color=None, dashArray=None, **kw):
+        """rectangular link annotation w.r.t the current user transform.
+           if the transform is skewed/rotated the absolute rectangle will use the max/min x/y
+        """
         destination = self._bookmarkReference(destinationname) # permitted to be undefined... must bind later...
-        Rect = self._absRect(Rect)
+        Rect = self._absRect(Rect,relative)
         kw["Rect"] = Rect
         kw["Contents"] = contents
         kw["Destination"] = destination
-        self._addAnnotation(pdfdoc.LinkAnnotation(**kw), name, addtopage)
-
-    def linkRect(self, contents, destinationname, Rect=None, addtopage=1, name=None, **kw):
-        """rectangular link annotation w.r.t the current user transform.
-           if the transform is skewed/rotated the absolute rectangle will use the max/min x/y
-        """
-        return self.linkAbsolute(contents, destinationname, Rect, addtopage, name, **kw)
+        _annFormat(kw,color,thickness,dashArray)
+        return self._addAnnotation(pdfdoc.LinkAnnotation(**kw), name, addtopage)
 
     def linkURL(self, url, rect, relative=0, thickness=0, color=None, dashArray=None, kind="URI", **kw):
         """Create a rectangular URL 'hotspot' in the given rectangle.
@@ -798,17 +812,7 @@
             raise ValueError("Unknown linkURI kind '%s'" % kind)
 
         ann["A"] = A
-
-        # now for formatting stuff.
-        if color:
-            ann["C"] = PDFArray([color.red, color.green, color.blue])
-        border = [0,0,0]
-        if thickness:
-            border[2] = thickness
-        if dashArray:
-            border.append(PDFArray(dashArray))
-        ann["Border"] = PDFArray(border)
-
+        _annFormat(ann,color,thickness,dashArray)
         self._addAnnotation(ann)
 
     def _addAnnotation(self, annotation, name=None, addtopage=1):
@@ -1257,6 +1261,7 @@
             leading = size * 1.2
         self._leading = leading
         font = pdfmetrics.getFont(self._fontname)
+
         self._dynamicFont = getattr(font, '_dynamicFont', 0)
         if not self._dynamicFont:
             pdffontname = self._doc.getInternalFontName(psfontname)
@@ -1268,12 +1273,8 @@
         if leading is None: leading = self._leading
         self.setFont(self._fontname, size, leading)
 
-    def stringWidth(self, text, fontName, fontSize, encoding=None):
+    def stringWidth(self, text, fontName, fontSize):
         "gets width of a string in the given font and size"
-        if encoding is not None:
-            from reportlab.lib import logger
-            logger.warnOnce('encoding argument to Canvas.stringWidth is deprecated and has no effect!')
-        #if encoding is None: encoding = self._doc.encoding
         return pdfmetrics.stringWidth(text, fontName, fontSize)
 
     # basic graphics modes
@@ -1301,10 +1302,10 @@
     def setDash(self, array=[], phase=0):
         """Two notations.  pass two numbers, or an array and phase"""
         if type(array) == IntType or type(array) == FloatType:
-            self._code.append('[%s] %s d' % (array, phase))
+            self._code.append('[%s %s] 0 d' % (array, phase))
         elif type(array) == ListType or type(array) == TupleType:
             assert phase >= 0, "phase is a length in user space"
-            textarray =  ' '.join(map(str, array))
+            textarray = ' '.join(map(str, array))
             self._code.append('[%s] %s d' % (textarray, phase))
 
     # path stuff - the separate path object builds it
@@ -1439,6 +1440,15 @@
             transDict[key] = value
         self._pageTransition = transDict
 
+    def getCurrentPageContent(self):
+        """Return uncompressed contents of current page buffer.
+
+        This is useful in creating test cases and assertions of what
+        got drawn, without necessarily saving pages to disk"""
+        return '\n'.join(self._code)
+
+
+
 if _instanceEscapePDF:
     import new
     Canvas._escape = new.instancemethod(_instanceEscapePDF,None,Canvas)
--- a/reportlab/pdfgen/textobject.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/pdfgen/textobject.py	Wed Apr 05 15:18:32 2006 +0000
@@ -232,6 +232,7 @@
         self._fontname = psfontname
         self._fontsize = size
         font = pdfmetrics.getFont(self._fontname)
+
         self._dynamicFont = getattr(font, '_dynamicFont', 0)
         if self._dynamicFont:
             self._curSubset = -1
@@ -250,6 +251,7 @@
             leading = size * 1.2
         self._leading = leading
         font = pdfmetrics.getFont(self._fontname)
+
         self._dynamicFont = getattr(font, '_dynamicFont', 0)
         if self._dynamicFont:
             self._curSubset = -1
@@ -303,31 +305,44 @@
 
     def _formatText(self, text):
         "Generates PDF text output operator(s)"
+        canv = self._canvas
+        font = pdfmetrics.getFont(self._fontname)
+        R = []
         if self._dynamicFont:
             #it's a truetype font and should be utf8.  If an error is raised,
+            for subset, t in font.splitString(text, canv._doc):
+                if subset != self._curSubset:
+                    pdffontname = font.getSubsetInternalName(subset, canv._doc)
+                    R.append("%s %s Tf %s TL" % (pdffontname, fp_str(self._fontsize), fp_str(self._leading)))
+                    self._curSubset = subset
+                R.append("(%s) Tj" % canv._escape(t))
+        elif font._multiByte:
+            #all the fonts should really work like this - let them know more about PDF...
+            R.append("%s %s Tf %s TL" % (
+                canv._doc.getInternalFontName(font.fontName),
+                fp_str(self._fontsize),
+                fp_str(self._leading)
+                ))
+            R.append("(%s) Tj" % font.formatForPdf(text))
             
-            results = []
-            font = pdfmetrics.getFont(self._fontname)
-            try: #assume UTF8
-                stuff = font.splitString(text, self._canvas._doc)
-            except UnicodeDecodeError:
-                #assume latin1 as fallback
-                from reportlab.pdfbase.ttfonts import latin1_to_utf8
-                from reportlab.lib.logger import warnOnce
-                warnOnce('non-utf8 data fed to truetype font, assuming latin-1 data')
-                text = latin1_to_utf8(text)
-                stuff = font.splitString(text, self._canvas._doc)
-            for subset, chunk in stuff:
-                if subset != self._curSubset:
-                    pdffontname = font.getSubsetInternalName(subset, self._canvas._doc)
-                    results.append("%s %s Tf %s TL" % (pdffontname, fp_str(self._fontsize), fp_str(self._leading)))
-                    self._curSubset = subset
-                chunk = self._canvas._escape(chunk)
-                results.append("(%s) Tj" % chunk)
-            return string.join(results, ' ')
         else:
-            text = self._canvas._escape(text)
-            return "(%s) Tj" % text
+            #convert to T1  coding
+            fc = font
+            if not isinstance(text,unicode):
+                try:
+                    text = text.decode('utf8')
+                except UnicodeDecodeError,e:
+                    i,j = e.args[2:4]
+                    raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],text[i-10:i],text[i:j],text[j:j+10]),)))
+
+            for f, t in pdfmetrics.unicode2T1(text,[font]+font.substitutionFonts):
+                if f!=fc:
+                    R.append("%s %s Tf %s TL" % (canv._doc.getInternalFontName(f.fontName), fp_str(self._fontsize), fp_str(self._leading)))
+                    fc = f
+                R.append("(%s) Tj" % canv._escape(t))
+            if font!=fc:
+                R.append("%s %s Tf %s TL" % (canv._doc.getInternalFontName(self._fontname), fp_str(self._fontsize), fp_str(self._leading)))
+        return ' '.join(R)
 
     def _textOut(self, text, TStar=0):
         "prints string at current point, ignores text cursor"
--- a/reportlab/platypus/doctemplate.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/doctemplate.py	Wed Apr 05 15:18:32 2006 +0000
@@ -81,11 +81,13 @@
        use NextPageTemplate which creates an ActionFlowable.
     '''
     def __init__(self,action=()):
+        #must call super init to ensure it has a width and height (of zero),
+        #as in some cases the packer might get called on it...
+        Flowable.__init__(self)
         if type(action) not in (ListType, TupleType):
             action = (action,)
         self.action = tuple(action)
 
-
     def apply(self,doc):
         '''
         This is called by the doc.build processing to allow the instance to
@@ -130,6 +132,10 @@
     def __init__(self,ix,resume=0):
         ActionFlowable.__init__(self,('currentFrame',ix,resume))
 
+class NullActionFlowable(ActionFlowable):
+    def apply(self):
+        pass
+
 class _FrameBreak(LCActionFlowable):
     '''
     A special ActionFlowable that allows setting doc._nextFrameIndex
@@ -570,7 +576,7 @@
         if i:
             if not getattr(flowables[i],'locChanger',None): i += 1
             K = KeepTogether(flowables[:i])
-            for f in K._flowables:
+            for f in K._content:
                 f.keepWithNext = 0
             del flowables[:i]
             flowables.insert(0,K)
@@ -619,16 +625,17 @@
                 else:
                     n = 0
                 if n:
-                    if self.frame.add(S[0], self.canv, trySplit=0):
-                        self._curPageFlowableCount = self._curPageFlowableCount + 1
-                        self.afterFlowable(S[0])
-                    else:
-                        ident = "Splitting error(n==%d) on page %d in\n%s" % (n,self.page,self._fIdent(f,30,self.frame))
-                        #leave to keep apart from the raise
-                        raise LayoutError(ident)
-                    del S[0]
-                    for f in xrange(n-1):
-                        flowables.insert(f,S[f])    # put split flowables back on the list
+                    if not isinstance(S[0],(PageBreak,SlowPageBreak,ActionFlowable)):
+                        if self.frame.add(S[0], self.canv, trySplit=0):
+                            self._curPageFlowableCount = self._curPageFlowableCount + 1
+                            self.afterFlowable(S[0])
+                        else:
+                            ident = "Splitting error(n==%d) on page %d in\n%s" % (n,self.page,self._fIdent(f,30,self.frame))
+                            #leave to keep apart from the raise
+                            raise LayoutError(ident)
+                        del S[0]
+                    for i,f in enumerate(S):
+                        flowables.insert(i,f)   # put split flowables back on the list
                 else:
                     if hasattr(f,'_postponed'):
                         ident = "Flowable %s too large on page %d" % (self._fIdent(f,30,self.frame), self.page)
--- a/reportlab/platypus/flowables.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/flowables.py	Wed Apr 05 15:18:32 2006 +0000
@@ -76,6 +76,11 @@
         self._traceInfo = None
         self._showBoundary = None
 
+        #many flowables handle text and must be processed in the
+        #absence of a canvas.  tagging them with their encoding
+        #helps us to get conversions right.  Use Python codec names.
+        self.encoding = None        
+
 
     def _drawOn(self,canv):
         '''ensure canv is set on and then draw'''
@@ -410,7 +415,7 @@
     def wrap(self, availWidth, availHeight):
         self.width = availWidth
         self.height = availHeight
-        return (availWidth,availHeight)  #step back a point
+        return (availWidth,availHeight-1e-8)  #step back a point
 
     def draw(self):
         pass
@@ -435,7 +440,7 @@
             return (availWidth, availHeight)
         return (0, 0)
 
-def _listWrapOn(F,availWidth,canv,mergeSpace=1,obj=None):
+def _listWrapOn(F,availWidth,canv,mergeSpace=1,obj=None,dims=None):
     '''return max width, required height for a list of flowables F'''
     W = 0
     H = 0
@@ -443,6 +448,7 @@
     atTop = 1
     for f in F:
         w,h = f.wrapOn(canv,availWidth,0xfffffff)
+        if dims is not None: dims.append((w,h))
         if w<=_FUZZ or h<=_FUZZ: continue
         W = max(W,w)
         H += h
@@ -465,13 +471,30 @@
     assert not [x for x in V if isinstance(x,LCActionFlowable)],'LCActionFlowables not allowed in sublists'
     return V
 
-class KeepTogether(Flowable):
+class _ContainerSpace:  #Abstract some common container like behaviour
+    def getSpaceBefore(self):
+        for c in self._content:
+            if not hasattr(c,'frameAction'):
+                return c.getSpaceBefore()
+        return 0
+
+    def getSpaceAfter(self,content=None):
+        #this needs 2.4
+        #for c in reversed(content or self._content):
+        reverseContent = (content or self._content)[:]
+        reverseContent.reverse()
+        for c in reverseContent:
+            if not hasattr(c,'frameAction'):
+                return c.getSpaceAfter()
+        return 0
+
+class KeepTogether(_ContainerSpace,Flowable):
     def __init__(self,flowables,maxHeight=None):
-        self._flowables = _flowableSublist(flowables)
+        self._content = _flowableSublist(flowables)
         self._maxHeight = maxHeight
 
     def __repr__(self):
-        f = self._flowables
+        f = self._content
         L = map(repr,f)
         import string
         L = "\n"+string.join(L, "\n")
@@ -479,17 +502,34 @@
         return "KeepTogether(%s,maxHeight=%s) # end KeepTogether" % (L,self._maxHeight)
 
     def wrap(self, aW, aH):
-        W,H = _listWrapOn(self._flowables,aW,self.canv)
-        self._CPage = (H>aH) and (not self._maxHeight or H<=self._maxHeight)
+        dims = []
+        W,H = _listWrapOn(self._content,aW,self.canv,dims=dims)
+        self._H = H
+        self._H0 = dims and dims[0][1] or 0
+        self._wrapInfo = aW,aH
         return W, 0xffffff  # force a split
 
     def split(self, aW, aH):
-        S = getattr(self,'_CPage',1) and [CondPageBreak(aH+1)] or []
-        for f in self._flowables: S.append(f)
+        if getattr(self,'_wrapInfo',None)!=(aW,aH): self.wrap(aW,aH)
+        S = self._content[:]
+        C0 = self._H>aH and (not self._maxHeight or aH>self._maxHeight)
+        C1 = self._H0>aH
+        if C0 or C1:
+            if C0:
+                from doctemplate import FrameBreak
+                A = FrameBreak
+            else:
+                from doctemplate import NullActionFlowable
+                A = NullActionFlowable
+            S.insert(0,A())
         return S
 
-    def identity(self):
-        return "<KeepTogether at %s%s> containing :%s" % (hex(id(self)),self._frameName(),"\n".join([f.identity() for f in self._flowables]))
+    def identity(self, maxLen=None):
+        msg = "<KeepTogether at %s%s> containing :%s" % (hex(id(self)),self._frameName(),"\n".join([f.identity() for f in self._content]))
+        if maxLen:
+            return msg[0:maxLen]
+        else:
+            return msg
 
 class Macro(Flowable):
     """This is not actually drawn (i.e. it has zero height)
@@ -531,6 +571,12 @@
         self.ypad = ypad
         self._side = side
 
+    def getSpaceBefore(self):
+        return max(self.P.getSpaceBefore(),self.I.getSpaceBefore())
+
+    def getSpaceAfter(self):
+        return max(self.P.getSpaceAfter(),self.I.getSpaceAfter())
+
     def wrap(self,availWidth,availHeight):
         wI, hI = self.I.wrap(availWidth,availHeight)
         self.wI = wI
@@ -643,23 +689,7 @@
         self.trailer = _flowableSublist(trailer)
         self.header = _flowableSublist(header)
 
-class _Container:   #Abstract some common container like behaviour
-    def getSpaceBefore(self):
-        for c in self._content:
-            if not hasattr(c,'frameAction'):
-                return c.getSpaceBefore()
-        return 0
-
-    def getSpaceAfter(self,content=None):
-        #this needs 2.4
-        #for c in reversed(self._content):
-        reverseContent = (content or self._content)[:]
-        reverseContent.reverse()
-        for c in reverseContent:
-            if not hasattr(c,'frameAction'):
-                return c.getSpaceAfter()
-        return 0
-
+class _Container(_ContainerSpace):  #Abstract some common container like behaviour
     def drawOn(self, canv, x, y, _sW=0, scale=1.0, content=None, aW=None):
         '''we simulate being added to a frame'''
         pS = 0
@@ -805,7 +835,10 @@
         return self.maxWidth - self._leftExtraIndent - self._rightExtraIndent
 
     def identity(self, maxLen=None):
-        return "<%s at %s%s%s> size=%sx%s" % (self.__class__.__name__, hex(id(self)), self._frameName(), self.name and ' name="%s"'%self.name or '', fp_str(self.maxWidth),fp_str(self.maxHeight))
+        return "<%s at %s%s%s> size=%sx%s" % (self.__class__.__name__, hex(id(self)), self._frameName(),
+                getattr(self,'name','') and (' name="%s"'% getattr(self,'name','')) or '',
+                getattr(self,'maxWidth','') and (' maxWidth=%s'%fp_str(getattr(self,'maxWidth',0))) or '',
+                getattr(self,'maxHeight','')and (' maxHeight=%s' % fp_str(getattr(self,'maxHeight')))or '')
 
     def wrap(self,availWidth,availHeight):
         from doctemplate import LayoutError
--- a/reportlab/platypus/para.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/para.py	Wed Apr 05 15:18:32 2006 +0000
@@ -1275,7 +1275,7 @@
             needatleast = state["leading"]
         else:
             needatleast = self.style1.leading
-        if availableHeight < needatleast:
+        if availableHeight<=needatleast:
             self.cansplit = 0
             #if debug:
             #    print "CANNOT COMPILE, NEED AT LEAST", needatleast, 'AVAILABLE', availableHeight
--- a/reportlab/platypus/paragraph.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/paragraph.py	Wed Apr 05 15:18:32 2006 +0000
@@ -2,18 +2,30 @@
 #see license.txt for license details
 #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/paragraph.py
 __version__=''' $Id$ '''
-from string import split, strip, join, whitespace, find
+from string import join, whitespace, find
 from operator import truth
 from types import StringType, ListType
-from reportlab.pdfbase.pdfmetrics import stringWidth
+from reportlab.pdfbase.pdfmetrics import stringWidth, getFont
 from reportlab.platypus.paraparser import ParaParser
 from reportlab.platypus.flowables import Flowable
 from reportlab.lib.colors import Color
 from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
 from reportlab.lib.utils import _className
+from reportlab.lib.textsplit import wordSplit
 from copy import deepcopy
 from reportlab.lib.abag import ABag
 
+
+#on UTF8 branch, split and strip must be unicode-safe!
+def split(text, delim=' '):
+    if type(text) is str: text = text.decode('utf8')
+    if type(delim) is str: delim = delim.decode('utf8')
+    return [uword.encode('utf8') for uword in text.split(delim)]
+
+def strip(text):
+    if type(text) is str: text = text.decode('utf8')
+    return text.strip().encode('utf8')
+
 class ParaLines(ABag):
     """
     class ParaLines contains the broken into lines representation of Paragraphs
@@ -126,9 +138,18 @@
                     xtraState.underlines.append( (xtraState.underline_x, cur_x, xtraState.underlineColor) )
                     xtraState.underlineColor = xtraState.textColor
                     xtraState.underline_x = cur_x
+            if not xtraState.link and f.link:
+                xtraState.link = f.link
+                xtraState.link_x = cur_x
+            elif xtraState.link and f.link is not xtraState.link:
+                    spacelen = tx._canvas.stringWidth(' ', tx._fontname, tx._fontsize)
+                    xtraState.links.append( (xtraState.link_x, cur_x-spacelen, xtraState.link) )
+                    xtraState.link = None
             cur_x = cur_x + txtlen
     if xtraState.underline:
         xtraState.underlines.append( (xtraState.underline_x, cur_x, xtraState.underlineColor) )
+    if xtraState.link:
+        xtraState.links.append( (xtraState.link_x, cur_x, xtraState.link) )
 
 def _leftDrawParaLineX( tx, offset, line, last=0):
     setXPos(tx,offset)
@@ -175,7 +196,7 @@
         def _sameFrag(f,g):
             'returns 1 if two ParaFrags map out the same'
             if hasattr(f,'cbDefn') or hasattr(g,'cbDefn'): return 0
-            for a in ('fontName', 'fontSize', 'textColor', 'rise', 'underline'):
+            for a in ('fontName', 'fontSize', 'textColor', 'rise', 'underline', 'link'):
                 if getattr(f,a)!=getattr(g,a): return 0
             return 1
 
@@ -345,7 +366,7 @@
             if j==lim:
                 i=i+1
 
-def _do_under_lines(i, t_off, tx):
+def _do_under_line(i, t_off, tx):
     y = tx.XtraState.cur_y - i*tx.XtraState.style.leading - tx.XtraState.f.fontSize/8.0 # 8.0 factor copied from para.py
     text = join(tx.XtraState.lines[i][1])
     textlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize)
@@ -364,6 +385,24 @@
     xtraState.underline=0
     xtraState.underlineColor=None
 
+def _do_link_line(i, t_off, tx):
+    xs = tx.XtraState
+    leading = xs.style.leading
+    y = xs.cur_y - i*leading - xs.f.fontSize/8.0 # 8.0 factor copied from para.py
+    text = join(xs.lines[i][1])
+    textlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize)
+    tx._canvas.linkRect("", xs.link, (t_off, y, t_off+textlen, y+leading), relative=1)
+
+def _do_link(i, t_off, tx):
+    xs = tx.XtraState
+    leading = xs.style.leading
+    y = xs.cur_y - i*leading - xs.f.fontSize/8.0 # 8.0 factor copied from para.py
+    for x1,x2,link in xs.links:
+        tx._canvas.line(t_off+x1, y, t_off+x2, y)
+        tx._canvas.linkRect("", link, (t_off+x1, y, t_off+x2, y+leading), relative=1)
+    xs.links = []
+    xs.link=None
+
 class Paragraph(Flowable):
     """ Paragraph(text, style, bulletText=None, caseSensitive=1)
         text a string of stuff to go into the paragraph.
@@ -387,11 +426,11 @@
 
         It will also be able to handle any MathML specified Greek characters.
     """
-    def __init__(self, text, style, bulletText = None, frags=None, caseSensitive=1):
+    def __init__(self, text, style, bulletText = None, frags=None, caseSensitive=1, encoding='utf8'):
         self.caseSensitive = caseSensitive
+        self.encoding = encoding
         self._setup(text, style, bulletText, frags, cleanBlockQuotedText)
 
-
     def __repr__(self):
         import string
         n = self.__class__.__name__
@@ -431,7 +470,12 @@
         leftIndent = self.style.leftIndent
         first_line_width = availWidth - (leftIndent+self.style.firstLineIndent) - self.style.rightIndent
         later_widths = availWidth - leftIndent - self.style.rightIndent
-        self.blPara = self.breakLines([first_line_width, later_widths])
+
+        if self.style.wordWrap == 'CJK':
+            #use Asian text wrap algorithm to break characters
+            self.blPara = self.breakLinesCJK([first_line_width, later_widths])
+        else:
+            self.blPara = self.breakLines([first_line_width, later_widths])
         self.height = len(self.blPara.lines) * self.style.leading
         return (self.width, self.height)
 
@@ -540,18 +584,19 @@
             fontSize = f.fontSize
             fontName = f.fontName
             words = hasattr(f,'text') and split(f.text, ' ') or f.words
-            spaceWidth = stringWidth(' ', fontName, fontSize)
+            spaceWidth = stringWidth(' ', fontName, fontSize, self.encoding)
             cLine = []
             currentWidth = - spaceWidth   # hack to get around extra space for word 1
             for word in words:
-                wordWidth = stringWidth(word, fontName, fontSize)
+                #this underscores my feeling that Unicode throughout would be easier!
+                wordWidth = stringWidth(word, fontName, fontSize, self.encoding)
                 newWidth = currentWidth + spaceWidth + wordWidth
-                if newWidth<=maxWidth or len(cLine)==0:
+                if newWidth <= maxWidth or len(cLine) == 0: 
                     # fit one more on this line
                     cLine.append(word)
                     currentWidth = newWidth
                 else:
-                    if currentWidth>self.width: self.width = currentWidth
+                    if currentWidth > self.width: self.width = currentWidth
                     #end of line
                     lines.append((maxWidth - currentWidth, cLine))
                     cLine = [word]
@@ -658,6 +703,50 @@
 
         return lines
 
+
+    def breakLinesCJK(self, width):
+        """Initially, the dumbest possible wrapping algorithm.
+        Cannot handle font variations."""
+
+
+        if type(width) <> ListType: maxWidths = [width]
+        else: maxWidths = width
+        lines = []
+        lineno = 0
+        style = self.style
+        fFontSize = float(style.fontSize)
+
+        #for bullets, work out width and ensure we wrap the right amount onto line one
+        _handleBulletWidth(self.bulletText, style, maxWidths)
+
+        maxWidth = maxWidths[0]
+
+        self.height = 0
+
+        #for now we only handle one fragment.  Need to generalize this quickly.
+        if len(self.frags) > 1:
+            raise ValueError('CJK Wordwrap can only handle one fragment per paragraph for now')
+        elif len(self.frags) == 0:
+            return ParaLines(kind=0, fontSize=style.fontSize, fontName=style.fontName,
+                            textColor=style.textColor, lines=[])
+
+        f = self.frags[0]
+
+        if hasattr(f,'text'):
+            text = f.text
+        else:
+            text = ''.join(getattr(f,'words',[]))
+
+        from reportlab.lib.textsplit import wordSplit
+        lines = wordSplit(text, maxWidths[0], f.fontName, f.fontSize)
+        #the paragraph drawing routine assumes multiple frags per line, so we need an
+        #extra list like this
+        #  [space, [text]]
+        #
+        wrappedLines = [(sp, [line]) for (sp, line) in lines]
+        return f.clone(kind=0, lines=wrappedLines)
+
+
     def beginText(self, x, y):
         return self.canv.beginText(x, y)
 
@@ -740,24 +829,27 @@
                 #now the font for the rest of the paragraph
                 tx.setFont(f.fontName, f.fontSize, style.leading)
                 t_off = dpl( tx, offset, lines[0][0], lines[0][1], noJustifyLast and nLines==1)
-                if f.underline:
-                    tx.XtraState=ABag()
-                    tx.XtraState.cur_y = cur_y
-                    tx.XtraState.f = f
-                    tx.XtraState.style = style
-                    tx.XtraState.lines = lines
-                    tx.XtraState.underlines=[]
-                    tx.XtraState.underlineColor=None
+                if f.underline or f.link:
+                    xs = tx.XtraState=ABag()
+                    xs.cur_y = cur_y
+                    xs.f = f
+                    xs.style = style
+                    xs.lines = lines
+                    xs.underlines=[]
+                    xs.underlineColor=None
+                    xs.links=[]
+                    xs.link=f.link
                     canvas.setStrokeColor(f.textColor)
-                    _do_under_lines(0, t_off+leftIndent, tx)
+                    if f.underline: _do_under_line(0, t_off+leftIndent, tx)
+                    if f.link: _do_link_line(0, t_off+leftIndent, tx)
 
                     #now the middle of the paragraph, aligned with the left margin which is our origin.
-                    for i in range(1, nLines):
+                    for i in xrange(1, nLines):
                         t_off = dpl( tx, _offsets[i], lines[i][0], lines[i][1], noJustifyLast and i==lim)
-                        if f.underline:
-                            _do_under_lines(i, t_off+leftIndent, tx)
+                        if f.underline: _do_under_line(i, t_off+leftIndent, tx)
+                        if f.link: _do_link_line(i, t_off+leftIndent, tx)
                 else:
-                    for i in range(1, nLines):
+                    for i in xrange(1, nLines):
                         dpl( tx, _offsets[i], lines[i][0], lines[i][1], noJustifyLast and i==lim)
             else:
                 f = lines[0]
@@ -779,16 +871,18 @@
 
                 #set up the font etc.
                 tx = self.beginText(cur_x, cur_y)
-                tx.XtraState=ABag()
-                tx.XtraState.textColor=None
-                tx.XtraState.rise=0
-                tx.XtraState.underline=0
-                tx.XtraState.underlines=[]
-                tx.XtraState.underlineColor=None
+                xs = tx.XtraState=ABag()
+                xs.textColor=None
+                xs.rise=0
+                xs.underline=0
+                xs.underlines=[]
+                xs.underlineColor=None
+                xs.links=[]
+                xs.link=None
                 tx.setLeading(style.leading)
-                tx.XtraState.cur_y = cur_y
-                tx.XtraState.f = f
-                tx.XtraState.style = style
+                xs.cur_y = cur_y
+                xs.f = f
+                xs.style = style
                 #f = lines[0].words[0]
                 #tx._setFont(f.fontName, f.fontSize)
 
@@ -796,12 +890,14 @@
                 tx._fontname,tx._fontsize = None, None
                 t_off = dpl( tx, offset, lines[0], noJustifyLast and nLines==1)
                 _do_under(0, t_off+leftIndent, tx)
+                _do_link(0, t_off+leftIndent, tx)
 
                 #now the middle of the paragraph, aligned with the left margin which is our origin.
                 for i in range(1, nLines):
                     f = lines[i]
                     t_off = dpl( tx, _offsets[i], f, noJustifyLast and i==lim)
                     _do_under(i, t_off+leftIndent, tx)
+                    _do_link(i, t_off+leftIndent, tx)
 
             canvas.drawText(tx)
             canvas.restoreState()
--- a/reportlab/platypus/paraparser.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/paraparser.py	Wed Apr 05 15:18:32 2006 +0000
@@ -4,7 +4,7 @@
 __version__=''' $Id$ '''
 import string
 import re
-from types import TupleType
+from types import TupleType, UnicodeType, StringType
 import sys
 import os
 import copy
@@ -13,7 +13,6 @@
 from reportlab.lib.abag import ABag
 
 from reportlab.lib import xmllib
-_xmllib_newStyle = 1
 
 from reportlab.lib.colors import toColor, white, black, red, Color
 from reportlab.lib.fonts import tt2ps, ps2tt
@@ -108,6 +107,18 @@
                 'backcolor':('backColor',toColor),
                 'bgcolor':('backColor',toColor),
                 }
+#things which are valid font attributes
+_linkAttrMap = {'size': ('fontSize', _num),
+                'face': ('fontName', None),
+                'name': ('fontName', None),
+                'fg':   ('textColor', toColor),
+                'color':('textColor', toColor),
+                'backcolor':('backColor',toColor),
+                'bgcolor':('backColor',toColor),
+                'dest': ('link', None),
+                'destination': ('link', None),
+                'target': ('link', None),
+                }
 
 def _addAttributeNames(m):
     K = m.keys()
@@ -136,264 +147,132 @@
 #with additions suggested by Christoph Zwerschke who also suggested the
 #numeric entity names that follow.
 greeks = {
-    'Alpha': 'A',
-    'Beta': 'B',
-    'Chi': 'C',
-    'Delta': 'D',
-    'Epsilon': 'E',
-    'Eta': 'H',
-    'Gamma': 'G',
-    'Iota': 'I',
-    'Kappa': 'K',
-    'Lambda': 'L',
-    'Mu': 'M',
-    'Nu': 'N',
-    'Omega': 'W',
-    'Omicron': 'O',
-    'Phi': 'F',
-    'Pi': 'P',
-    'Psi': 'Y',
-    'Rho': 'R',
-    'Sigma': 'S',
-    'Tau': 'T',
-    'Theta': 'Q',
-    'Upsilon': 'U',
-    'Xi': 'X',
-    'Zeta': 'Z',
-    'alefsym': '\xc0',
-    'alpha': 'a',
-    'and': '\xd9',
-    'ang': '\xd0',
-    'asymp': '\xbb',
-    'beta': 'b',
-    'bull': '\xb7',
-    'cap': '\xc7',
-    'chi': 'c',
-    'clubs': '\xa7',
-    'cong': '@',
-    'cup': '\xc8',
-    'dArr': '\xdf',
-    'darr': '\xaf',
-    'delta': 'd',
-    'diams': '\xa8',
-    'empty': '\xc6',
-    'epsilon': 'e',
-    'epsiv': 'e',
-    'equiv': '\xba',
-    'eta': 'h',
-    'euro': '\xa0',
-    'exist': '$',
-    'forall': '"',
-    'frasl': '\xa4',
-    'gamma': 'g',
-    'ge': '\xb3',
-    'hArr': '\xdb',
-    'harr': '\xab',
-    'hearts': '\xa9',
-    'hellip': '\xbc',
-    'image': '\xc1',
-    'infin': '\xa5',
-    'int': '\xf2',
-    'iota': 'i',
-    'isin': '\xce',
-    'kappa': 'k',
-    'lArr': '\xdc',
-    'lambda': 'l',
-    'lang': '\xe1',
-    'larr': '\xac',
-    'lceil': '\xe9',
-    'le': '\xa3',
-    'lfloor': '\xeb',
-    'lowast': '*',
-    'loz': '\xe0',
-    'minus': '-',
-    'mu': 'm',
-    'nabla': '\xd1',
-    'ne': '\xb9',
-    'ni': "'",
-    'notin': '\xcf',
-    'nsub': '\xcb',
-    'nu': 'n',
-    'oline': '`',
-    'omega': 'w',
-    'omicron': 'o',
-    'oplus': '\xc5',
-    'or': '\xda',
-    'otimes': '\xc4',
-    'part': '\xb6',
-    'perp': '^',
-    'phi': 'j',
-    'phis': 'f',
-    'pi': 'p',
-    'piv': 'v',
-    'prime': '\xa2',
-    'prod': '\xd5',
-    'prop': '\xb5',
-    'psi': 'y',
-    'rArr': '\xde',
-    'radic': '\xd6',
-    'rang': '\xf1',
-    'rarr': '\xae',
-    'rceil': '\xf9',
-    'real': '\xc2',
-    'rfloor': '\xfb',
-    'rho': 'r',
-    'sdot': '\xd7',
-    'sigma': 's',
-    'sigmaf': 'V',
-    'sigmav': 'V',
-    'sim': '~',
-    'spades': '\xaa',
-    'sub': '\xcc',
-    'sube': '\xcd',
-    'sum': '\xe5',
-    'sup': '\xc9',
-    'supe': '\xca',
-    'tau': 't',
-    'there4': '\\',
-    'theta': 'q',
-    'thetasym': 'J',
-    'thetav': 'J',
-    'trade': '\xe4',
-    'uArr': '\xdd',
-    'uarr': '\xad',
-    'upsih': '\xa1',
-    'upsilon': 'u',
-    'weierp': '\xc3',
-    'xi': 'x',
-    'zeta': 'z',
-    }
-
-# mapping of xml character entities to symbol encoding
-symenc = {
-    # greek letters
-    913:'A', # Alpha
-    914:'B', # Beta
-    915:'G', # Gamma
-    916:'D', # Delta
-    917:'E', # Epsilon
-    918:'Z', # Zeta
-    919:'H', # Eta
-    920:'Q', # Theta
-    921:'I', # Iota
-    922:'K', # Kappa
-    923:'L', # Lambda
-    924:'M', # Mu
-    925:'N', # Nu
-    926:'X', # Xi
-    927:'O', # Omicron
-    928:'P', # Pi
-    929:'R', # Rho
-    931:'S', # Sigma
-    932:'T', # Tau
-    933:'U', # Upsilon
-    934:'F', # Phi
-    935:'C', # Chi
-    936:'Y', # Psi
-    937:'W', # Omega
-    945:'a', # alpha
-    946:'b', # beta
-    947:'g', # gamma
-    948:'d', # delta
-    949:'e', # epsilon
-    950:'z', # zeta
-    951:'h', # eta
-    952:'q', # theta
-    953:'i', # iota
-    954:'k', # kappa
-    955:'l', # lambda
-    956:'m', # mu
-    957:'n', # nu
-    958:'x', # xi
-    959:'o', # omicron
-    960:'p', # pi
-    961:'r', # rho
-    962:'V', # sigmaf
-    963:'s', # sigma
-    964:'t', # tau
-    965:'u', # upsilon
-    966:'j', # phi
-    967:'c', # chi
-    968:'y', # psi
-    969:'w', # omega
-    977:'J', # thetasym
-    978:'\241', # upsih
-    981:'f', # phis
-    982:'v', # piv
-    # mathematical symbols
-    8704:'"', # forall
-    8706:'\266', # part
-    8707:'$', # exist
-    8709:'\306', # empty
-    8711:'\321', # nabla
-    8712:'\316', # isin
-    8713:'\317', # notin
-    8715:'\'', # ni
-    8719:'\325', # prod
-    8721:'\345', # sum
-    8722:'-', # minus
-    8727:'*', # lowast
-    8730:'\326', # radic
-    8733:'\265', # prop
-    8734:'\245', # infin
-    8736:'\320', # ang
-    8869:'\331', # and
-    8870:'\332', # or
-    8745:'\307', # cap
-    8746:'\310', # cup
-    8747:'\362', # int
-    8756:'\\', # there4
-    8764:'~', # sim
-    8773:'@', # cong
-    8776:'\273', #asymp
-    8800:'\271', # ne
-    8801:'\272', # equiv
-    8804:'\243', # le
-    8805:'\263', # ge
-    8834:'\314', # sub
-    8835:'\311', # sup
-    8836:'\313', # nsub
-    8838:'\315', # sube
-    8839:'\312', # supe
-    8853:'\305', # oplus
-    8855:'\304', # otimes
-    8869:'^', # perp
-    8901:'\327', # sdot
-    9674:'\340', # loz
-    # technical symbols
-    8968:'\351', # lceil
-    8969:'\371', # rceil
-    8970:'\353', # lfloor
-    8971:'\373', # rfloor
-    9001:'\341', # lang
-    9002:'\361', # rang
-    # arrow symbols
-    8592:'\254', # larr
-    8593:'\255', # uarr
-    8594:'\256', # rarr
-    8595:'\257', # darr
-    8596:'\253', # harr
-    8656:'\334', # lArr
-    8657:'\335', # uArr
-    8658:'\336', # rArr
-    8659:'\337', # dArr
-    8660:'\333', # hArr
-    # divers symbols
-    8226:'\267', # bull
-    8230:'\274', # hellip
-    8242:'\242', # prime
-    8254:'`', # oline
-    8260:'\244', # frasl
-    8472:'\303', # weierp
-    8465:'\301', # image
-    8476:'\302', # real
-    8482:'\344', # trade
-    8364:'\240', # euro
-    8501:'\300', # alefsym
-    9824:'\252', # spades
-    9827:'\247', # clubs
-    9829:'\251', # hearts
-    9830:'\250' # diams
+    'alefsym': '\xe2\x84\xb5',
+    'Alpha': '\xce\x91',
+    'alpha': '\xce\xb1',
+    'and': '\xe2\x88\xa7',
+    'ang': '\xe2\x88\xa0',
+    'asymp': '\xe2\x89\x88',
+    'Beta': '\xce\x92',
+    'beta': '\xce\xb2',
+    'bull': '\xe2\x80\xa2',
+    'cap': '\xe2\x88\xa9',
+    'Chi': '\xce\xa7',
+    'chi': '\xcf\x87',
+    'clubs': '\xe2\x99\xa3',
+    'cong': '\xe2\x89\x85',
+    'cup': '\xe2\x88\xaa',
+    'darr': '\xe2\x86\x93',
+    'dArr': '\xe2\x87\x93',
+    'delta': '\xce\xb4',
+    'Delta': '\xe2\x88\x86',
+    'diams': '\xe2\x99\xa6',
+    'empty': '\xe2\x88\x85',
+    'Epsilon': '\xce\x95',
+    'epsilon': '\xce\xb5',
+    'epsiv': '\xce\xb5',
+    'equiv': '\xe2\x89\xa1',
+    'Eta': '\xce\x97',
+    'eta': '\xce\xb7',
+    'euro': '\xe2\x82\xac',
+    'exist': '\xe2\x88\x83',
+    'forall': '\xe2\x88\x80',
+    'frasl': '\xe2\x81\x84',
+    'Gamma': '\xce\x93',
+    'gamma': '\xce\xb3',
+    'ge': '\xe2\x89\xa5',
+    'harr': '\xe2\x86\x94',
+    'hArr': '\xe2\x87\x94',
+    'hearts': '\xe2\x99\xa5',
+    'hellip': '\xe2\x80\xa6',
+    'image': '\xe2\x84\x91',
+    'infin': '\xe2\x88\x9e',
+    'int': '\xe2\x88\xab',
+    'Iota': '\xce\x99',
+    'iota': '\xce\xb9',
+    'isin': '\xe2\x88\x88',
+    'Kappa': '\xce\x9a',
+    'kappa': '\xce\xba',
+    'Lambda': '\xce\x9b',
+    'lambda': '\xce\xbb',
+    'lang': '\xe2\x8c\xa9',
+    'larr': '\xe2\x86\x90',
+    'lArr': '\xe2\x87\x90',
+    'lceil': '\xef\xa3\xae',
+    'le': '\xe2\x89\xa4',
+    'lfloor': '\xef\xa3\xb0',
+    'lowast': '\xe2\x88\x97',
+    'loz': '\xe2\x97\x8a',
+    'minus': '\xe2\x88\x92',
+    'mu': '\xc2\xb5',
+    'Mu': '\xce\x9c',
+    'nabla': '\xe2\x88\x87',
+    'ne': '\xe2\x89\xa0',
+    'ni': '\xe2\x88\x8b',
+    'notin': '\xe2\x88\x89',
+    'nsub': '\xe2\x8a\x84',
+    'Nu': '\xce\x9d',
+    'nu': '\xce\xbd',
+    'oline': '\xef\xa3\xa5',
+    'omega': '\xcf\x89',
+    'Omega': '\xe2\x84\xa6',
+    'Omicron': '\xce\x9f',
+    'omicron': '\xce\xbf',
+    'oplus': '\xe2\x8a\x95',
+    'or': '\xe2\x88\xa8',
+    'otimes': '\xe2\x8a\x97',
+    'part': '\xe2\x88\x82',
+    'perp': '\xe2\x8a\xa5',
+    'Phi': '\xce\xa6',
+    'phi': '\xcf\x95',
+    'phis': '\xcf\x86',
+    'Pi': '\xce\xa0',
+    'pi': '\xcf\x80',
+    'piv': '\xcf\x96',
+    'prime': '\xe2\x80\xb2',
+    'prod': '\xe2\x88\x8f',
+    'prop': '\xe2\x88\x9d',
+    'Psi': '\xce\xa8',
+    'psi': '\xcf\x88',
+    'radic': '\xe2\x88\x9a',
+    'rang': '\xe2\x8c\xaa',
+    'rarr': '\xe2\x86\x92',
+    'rArr': '\xe2\x87\x92',
+    'rceil': '\xef\xa3\xb9',
+    'real': '\xe2\x84\x9c',
+    'rfloor': '\xef\xa3\xbb',
+    'Rho': '\xce\xa1',
+    'rho': '\xcf\x81',
+    'sdot': '\xe2\x8b\x85',
+    'Sigma': '\xce\xa3',
+    'sigma': '\xcf\x83',
+    'sigmaf': '\xcf\x82',
+    'sigmav': '\xcf\x82',
+    'sim': '\xe2\x88\xbc',
+    'spades': '\xe2\x99\xa0',
+    'sub': '\xe2\x8a\x82',
+    'sube': '\xe2\x8a\x86',
+    'sum': '\xe2\x88\x91',
+    'sup': '\xe2\x8a\x83',
+    'supe': '\xe2\x8a\x87',
+    'Tau': '\xce\xa4',
+    'tau': '\xcf\x84',
+    'there4': '\xe2\x88\xb4',
+    'Theta': '\xce\x98',
+    'theta': '\xce\xb8',
+    'thetasym': '\xcf\x91',
+    'thetav': '\xcf\x91',
+    'trade': '\xef\xa3\xaa',
+    'uarr': '\xe2\x86\x91',
+    'uArr': '\xe2\x87\x91',
+    'upsih': '\xcf\x92',
+    'Upsilon': '\xce\xa5',
+    'upsilon': '\xcf\x85',
+    'weierp': '\xe2\x84\x98',
+    'Xi': '\xce\x9e',
+    'xi': '\xce\xbe',
+    'Zeta': '\xce\x96',
+    'zeta': '\xce\xb6',
     }
 
 #------------------------------------------------------------------------
@@ -477,6 +356,15 @@
     def end_u( self ):
         self._pop(underline=1)
 
+    #### link
+    def start_link(self, attributes):
+        self._push(**self.getAttributes(attributes,_linkAttrMap))
+
+    def end_link(self):
+        frag = self._stack[-1]
+        del self._stack[-1]
+        assert frag.link!=None
+
     #### super script
     def start_super( self, attributes ):
         self._push(super=1)
@@ -498,21 +386,14 @@
     #### add symbol encoding
     def handle_charref(self, name):
         try:
-            if name[0] == 'x':
-                n = string.atoi(name[1:], 16)
+            if name[0]=='x':
+                n = int(name[1:],16)
             else:
-                n = string.atoi(name)
-        except string.atoi_error:
+                n = int(name)
+        except ValueError:
             self.unknown_charref(name)
             return
-        if 0 <=n<=255:
-            self.handle_data(chr(n))
-        elif symenc.has_key(n):
-            self._push(greek=1)
-            self.handle_data(symenc[n])
-            self._pop(greek=1)
-        else:
-            self.unknown_charref(name)
+        self.handle_data(unichr(n).encode('utf8'))
 
     def handle_entityref(self,name):
         if greeks.has_key(name):
@@ -536,7 +417,7 @@
         self._pop(greek=1)
 
     def start_font(self,attr):
-        apply(self._push,(),self.getAttributes(attr,_fontAttrMap))
+        self._push(**self.getAttributes(attr,_fontAttrMap))
 
     def end_font(self):
         self._pop()
@@ -555,6 +436,7 @@
         frag.rise = 0
         frag.underline = 0
         frag.greek = 0
+        frag.link = None
         if bullet:
             frag.fontName, frag.bold, frag.italic = ps2tt(style.bulletFontName)
             frag.fontSize = style.bulletFontSize
@@ -691,7 +573,7 @@
                 j = attrMap[k]
                 func = j[1]
                 try:
-                    A[j[0]] = (func is None) and v or apply(func,(v,))
+                    A[j[0]] = (func is None) and v or func(v)
                 except:
                     self._syntax_error('%s: invalid value %s'%(k,v))
             else:
@@ -764,6 +646,16 @@
         If errors occur None will be returned and the
         self.errors holds a list of the error messages.
         """
+        # AR 20040612 - when we feed Unicode strings in, sgmlop
+        # tries to coerce to ASCII.  Must intercept, coerce to
+        # any 8-bit encoding which defines most of 256 points,
+        # and revert at end.  Yuk.  Preliminary step prior to
+        # removal of parser altogether.
+        enc = self._enc = 'cp1252' #our legacy default
+        self._UNI = type(text) is UnicodeType
+        if self._UNI:
+            text = text.encode(enc)
+
         self._setup_for_parse(style)
         # the xmlparser requires that all text be surrounded by xml
         # tags, therefore we must throw some unused flags around the
@@ -784,6 +676,16 @@
             self._iReset()
         else:
             fragList = bFragList = None
+
+        if self._UNI:
+            #reconvert to unicode
+            if fragList:
+                for frag in fragList:
+                    frag.text = unicode(frag.text, self._enc)
+            if bFragList:
+                for frag in bFragList:
+                    frag.text = unicode(frag.text, self._enc)
+            
         return style, fragList, bFragList
 
     def _tt_parse(self,tt):
--- a/reportlab/platypus/tables.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/platypus/tables.py	Wed Apr 05 15:18:32 2006 +0000
@@ -40,6 +40,8 @@
         'alignment': 'LEFT',
         'background': (1,1,1),
         'valign': 'BOTTOM',
+        'href': None,
+        'destination':None,
         }
 
 LINECAPS={None: None, 'butt':0,'round':1,'projecting':2,'squared':2}
@@ -59,6 +61,8 @@
     alignment = 'LEFT'
     background = (1,1,1)
     valign = "BOTTOM"
+    href = None
+    destination = None
     def __init__(self, name, parent=None):
         self.name = name
         if parent is not None:
@@ -1251,6 +1255,15 @@
                 draw(x, y, v)
                 y -= leading
 
+        if cellstyle.href:
+            #external hyperlink
+            #print 'drawing URL %d,%d, %d, %d, %s' % (colpos, rowpos, colwidth, rowheight, cellstyle.href)
+            self.canv.linkURL(cellstyle.href, (colpos, rowpos, colpos + colwidth, rowpos + rowheight), relative=1)
+        if cellstyle.destination:
+            #external hyperlink
+            #print 'drawing destination %d,%d, %d, %d, %s' % (colpos, rowpos, colwidth, rowheight, cellstyle.destination)
+            self.canv.linkRect("", cellstyle.destination, Rect=(colpos, rowpos, colpos + colwidth, rowpos + rowheight), relative=1)
+        
 # for text,
 #   drawCentredString(self, x, y, text) where x is center
 #   drawRightString(self, x, y, text) where x is right
@@ -1308,6 +1321,10 @@
         new.topPadding = values[0]
     elif op == 'BOTTOMPADDING':
         new.bottomPadding = values[0]
+    elif op == 'HREF':
+        new.href = values[0]
+    elif op == 'DESTINATION':
+        new.destination = values[0]
 
 GRID_STYLE = TableStyle(
     [('GRID', (0,0), (-1,-1), 0.25, colors.black),
--- a/reportlab/rl_config.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/rl_config.py	Wed Apr 05 15:18:32 2006 +0000
@@ -20,16 +20,27 @@
 overlapAttachedSpace=       1                       #if set non false then adajacent flowable space after
                                                     #and space before are merged (max space is used).
 longTableOptimize =         0                       #default don't use Henning von Bargen's long table optimizations
+autoConvertEncoding  =      0                       #convert internally as needed (experimental)
 _FUZZ=                      1e-6                    #fuzz for layout arithmetic
 
 # places to look for T1Font information
 T1SearchPath =  (
+                'c:/Program Files/Adobe/Acrobat 9.0/Resource/Font', 
+                'c:/Program Files/Adobe/Acrobat 8.0/Resource/Font', 
+                'c:/Program Files/Adobe/Acrobat 7.0/Resource/Font', 
                 'c:/Program Files/Adobe/Acrobat 6.0/Resource/Font', #Win32, Acrobat 6
                 'c:/Program Files/Adobe/Acrobat 5.0/Resource/Font',     #Win32, Acrobat 5
                 'c:/Program Files/Adobe/Acrobat 4.0/Resource/Font', #Win32, Acrobat 4
                 '%(disk)s/Applications/Python %(sys_version)s/reportlab/fonts', #Mac?
+                '/usr/lib/Acrobat9/Resource/Font',      #Linux, Acrobat 5?
+                '/usr/lib/Acrobat8/Resource/Font',      #Linux, Acrobat 5?
+                '/usr/lib/Acrobat7/Resource/Font',      #Linux, Acrobat 5?
+                '/usr/lib/Acrobat6/Resource/Font',      #Linux, Acrobat 5?
                 '/usr/lib/Acrobat5/Resource/Font',      #Linux, Acrobat 5?
                 '/usr/lib/Acrobat4/Resource/Font',      #Linux, Acrobat 4
+                '/usr/local/Acrobat9/Resource/Font',    #Linux, Acrobat 5?
+                '/usr/local/Acrobat8/Resource/Font',    #Linux, Acrobat 5?
+                '/usr/local/Acrobat7/Resource/Font',    #Linux, Acrobat 5?
                 '/usr/local/Acrobat6/Resource/Font',    #Linux, Acrobat 5?
                 '/usr/local/Acrobat5/Resource/Font',    #Linux, Acrobat 5?
                 '/usr/local/Acrobat4/Resource/Font',    #Linux, Acrobat 4
@@ -51,13 +62,23 @@
                 )
 
 # places to look for CMap files - should ideally merge with above
-CMapSearchPath = ('/usr/lib/Acrobat6/Resource/CMap',
+CMapSearchPath = (
+                  '/usr/lib/Acrobat9/Resource/CMap',
+                  '/usr/lib/Acrobat8/Resource/CMap',
+                  '/usr/lib/Acrobat7/Resource/CMap',
+                  '/usr/lib/Acrobat6/Resource/CMap',
                   '/usr/lib/Acrobat5/Resource/CMap',
                   '/usr/lib/Acrobat4/Resource/CMap',
+                  '/usr/local/Acrobat9/Resource/CMap',
+                  '/usr/local/Acrobat8/Resource/CMap',
+                  '/usr/local/Acrobat7/Resource/CMap',
                   '/usr/local/Acrobat6/Resource/CMap',
                   '/usr/local/Acrobat5/Resource/CMap',
                   '/usr/local/Acrobat4/Resource/CMap',
                   'C:\\Program Files\\Adobe\\Acrobat\\Resource\\CMap',
+                  'C:\\Program Files\\Adobe\\Acrobat 9.0\\Resource\\CMap',
+                  'C:\\Program Files\\Adobe\\Acrobat 8.0\\Resource\\CMap',
+                  'C:\\Program Files\\Adobe\\Acrobat 7.0\\Resource\\CMap',
                   'C:\\Program Files\\Adobe\\Acrobat 6.0\\Resource\\CMap',
                   'C:\\Program Files\\Adobe\\Acrobat 5.0\\Resource\\CMap',
                   'C:\\Program Files\\Adobe\\Acrobat 4.0\\Resource\\CMap'
--- a/reportlab/test/test_graphics_charts.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_graphics_charts.py	Wed Apr 05 15:18:32 2006 +0000
@@ -131,7 +131,9 @@
 
 def sample3(drawing=None):
     "Add sample swatches to a diagram."
+
     d = drawing or Drawing(400, 200)
+
     swatches = Legend()
     swatches.alignment = 'right'
     swatches.x = 80
@@ -141,9 +143,12 @@
     swatches.columnMaximum = 4
     items = [(colors.red, 'before'), (colors.green, 'after')]
     swatches.colorNamePairs = items
+
     d.add(swatches, 'legend')
+
     return d
 
+
 def sample4pie():
     width = 300
     height = 150
--- a/reportlab/test/test_multibyte_jpn.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_multibyte_jpn.py	Wed Apr 05 15:18:32 2006 +0000
@@ -26,15 +26,17 @@
     def hDraw(self, c, msg, fnt, x, y):
         "Helper - draws it with a box around"
         c.setFont(fnt, 16, 16)
+        font = pdfmetrics.getFont(fnt)
         c.drawString(x, y, msg)
-        c.rect(x,y,pdfmetrics.stringWidth(msg, fnt, 16),16,stroke=1,fill=0)
+        width = font.stringWidth(msg, 16)
+        c.rect(x,y,width,16,stroke=1,fill=0)
 
     def test0(self):
         "A basic document drawing some strings"
 
         # if they do not have the Japanese font files, go away quietly
         try:
-            from reportlab.pdfbase.cidfonts import CIDFont, findCMapFile
+            from reportlab.pdfbase.cidfonts import CIDFont, findCMapFile, UnicodeCIDFont
             findCMapFile('90ms-RKSJ-H')
             findCMapFile('90msp-RKSJ-H')
             findCMapFile('UniJIS-UCS2-H')
@@ -80,16 +82,26 @@
         pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','EUC-H'))
         self.hDraw(c, '\xC5\xEC\xB5\xFE says Tokyo in EUC', 'HeiseiMin-W3-EUC-H', 100, 550)
 
-        if 0:
-            #this is super-slow until we do encoding caching.
-            pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','UniJIS-UCS2-H'))
-            def asciiToUCS2(text):
-                s = ''
-                for ch in text:
-                    s = s + chr(0) + ch
-                return s
-            self.hDraw(c, '\x67\x71\x4E\xAC' + asciiToUCS2(' says Tokyo in UCS2'),
-                       'HeiseiMin-W3-UniJIS-UCS2-H', 100, 525)
+        #this is super-slow until we do encoding caching.
+        pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','UniJIS-UCS2-H'))
+
+        def asciiToUCS2(text):
+            s = ''
+            for ch in text:
+                s = s + chr(0) + ch
+            return s
+        msg = '\x67\x71\x4E\xAC' + asciiToUCS2(' says Tokyo in UTF16')
+        self.hDraw(c, msg,'HeiseiMin-W3-UniJIS-UCS2-H', 100, 525)
+
+        #unicode font automatically supplies the encoding
+        pdfmetrics.registerFont(UnicodeCIDFont('HeiseiMin-W3'))
+
+        
+        msg = u'\u6771\u4EAC : Unicode font, unicode input'
+        self.hDraw(c, msg, 'HeiseiMin-W3', 100, 500)
+
+        msg = u'\u6771\u4EAC : Unicode font, utf8 input'.encode('utf8')
+        self.hDraw(c, msg, 'HeiseiMin-W3', 100, 475)
 
 
         # now try verticals
@@ -105,8 +117,33 @@
         height = c.stringWidth('\xC5\xEC\xB5\xFE vertical EUC', 'HeiseiMin-W3-EUC-V', 16)
         c.rect(425-8,650,16,-height)
 
+
+
+        from reportlab.platypus.paragraph import Paragraph
+        from reportlab.lib.styles import ParagraphStyle
+        jStyle = ParagraphStyle('jtext',
+                                fontName='HeiseiMin-W3',
+                                fontSize=12,
+                                wordWrap="CJK"
+                                )
+        
+        gatwickText = '\xe3\x82\xac\xe3\x83\x88\xe3\x82\xa6\xe3\x82\xa3\xe3\x83\x83\xe3\x82\xaf\xe7\xa9\xba\xe6\xb8\xaf\xe3\x81\xa8\xe9\x80\xa3\xe7\xb5\xa1\xe9\x80\x9a\xe8\xb7\xaf\xe3\x81\xa7\xe7\x9b\xb4\xe7\xb5\x90\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3\x82\x8b\xe5\x94\xaf\xe4\xb8\x80\xe3\x81\xae\xe3\x83\x9b\xe3\x83\x86\xe3\x83\xab\xe3\x81\xa7\xe3\x81\x82\xe3\x82\x8b\xe5\xbd\x93\xe3\x83\x9b\xe3\x83\x86\xe3\x83\xab\xe3\x81\xaf\xe3\x80\x81\xe8\xa1\x97\xe3\x81\xae\xe4\xb8\xad\xe5\xbf\x83\xe9\x83\xa8\xe3\x81\x8b\xe3\x82\x8930\xe5\x88\x86\xe3\x81\xae\xe5\xa0\xb4\xe6\x89\x80\xe3\x81\xab\xe3\x81\x94\xe3\x81\x96\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe5\x85\xa8\xe5\xae\xa2\xe5\xae\xa4\xe3\x81\xab\xe9\xab\x98\xe9\x80\x9f\xe3\x82\xa4\xe3\x83\xb3\xe3\x82\xbf\xe3\x83\xbc\xe3\x83\x8d\xe3\x83\x83\xe3\x83\x88\xe7\x92\xb0\xe5\xa2\x83\xe3\x82\x92\xe5\xae\x8c\xe5\x82\x99\xe3\x81\x97\xe3\x81\xa6\xe3\x81\x8a\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe3\x83\x95\xe3\x82\xa1\xe3\x83\x9f\xe3\x83\xaa\xe3\x83\xbc\xe3\x83\xab\xe3\x83\xbc\xe3\x83\xa0\xe3\x81\xaf5\xe5\x90\x8d\xe6\xa7\x98\xe3\x81\xbe\xe3\x81\xa7\xe3\x81\x8a\xe6\xb3\x8a\xe3\x82\x8a\xe3\x81\x84\xe3\x81\x9f\xe3\x81\xa0\xe3\x81\x91\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe3\x81\xbe\xe3\x81\x9f\xe3\x80\x81\xe3\x82\xa8\xe3\x82\xb0\xe3\x82\xbc\xe3\x82\xaf\xe3\x83\x86\xe3\x82\xa3\xe3\x83\x96\xe3\x83\xab\xe3\x83\xbc\xe3\x83\xa0\xe3\x81\xae\xe3\x81\x8a\xe5\xae\xa2\xe6\xa7\x98\xe3\x81\xaf\xe3\x80\x81\xe3\x82\xa8\xe3\x82\xb0\xe3\x82\xbc\xe3\x82\xaf\xe3\x83\x86\xe3\x82\xa3\xe3\x83\x96\xe3\x83\xa9\xe3\x82\xa6\xe3\x83\xb3\xe3\x82\xb8\xe3\x82\x92\xe3\x81\x94\xe5\x88\xa9\xe7\x94\xa8\xe3\x81\x84\xe3\x81\x9f\xe3\x81\xa0\xe3\x81\x91\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe4\xba\x8b\xe5\x89\x8d\xe3\x81\xab\xe3\x81\x94\xe4\xba\x88\xe7\xb4\x84\xe3\x81\x84\xe3\x81\x9f\xe3\x81\xa0\xe3\x81\x91\xe3\x82\x8b\xe3\x82\xbf\xe3\x82\xa4\xe3\x83\xa0\xe3\x83\x88\xe3\x82\xa5\xe3\x83\x95\xe3\x83\xa9\xe3\x82\xa4\xe3\x83\xbb\xe3\x83\x91\xe3\x83\x83\xe3\x82\xb1\xe3\x83\xbc\xe3\x82\xb8\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x81\xe7\xa9\xba\xe6\xb8\xaf\xe3\x81\xae\xe9\xa7\x90\xe8\xbb\x8a\xe6\x96\x99\xe9\x87\x91\xe3\x81\x8c\xe5\x90\xab\xe3\x81\xbe\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x8a\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82'
+
+        c.setFont('HeiseiMin-W3', 12)
+##        from reportlab.lib.textsplit import wordSplit
+##        y = 400
+##        splat = wordSplit(gatwickText, 250, 'HeiseiMin-W3', 12, encoding='utf8')
+##        for (line, extraSpace) in splat:
+##            c.drawString(100,y,line)
+##            y -= 14
+        jPara = Paragraph(gatwickText, jStyle)
+        jPara.wrap(250, 200)
+        #from pprint import pprint as pp
+        #pp(jPara.blPara)
+        jPara.drawOn(c, 100, 250)
+
         c.setFillColor(colors.purple)
-        tx = c.beginText(100, 250)
+        tx = c.beginText(100, 200)
         tx.setFont('Helvetica', 12)
         tx.textLines("""This document shows sample output in Japanese
         from the Reportlab PDF library.  This page shows the two fonts
@@ -119,6 +156,21 @@
         c.drawText(tx)
         c.setFont('Helvetica',10)
         c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber())
+
+
+
+        c.showPage()
+
+        c.setFont('Helvetica', 30)
+        c.drawString(100,700, 'Japanese TrueType Font Support')
+        msg = u'\u6771\u4EAC : Unicode font, utf8 input'.encode('utf8')
+        from reportlab.pdfbase.ttfonts import TTFont
+        pdfmetrics.registerFont(TTFont('MS Mincho','msmincho.ttf'))
+        c.setFont('MS Mincho', 30)
+        c.drawString(100,600, msg)
+        
+                                
+
         c.showPage()
 
         # realistic text sample
@@ -214,19 +266,81 @@
         c.setFont('Helvetica',10)
         c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber())
 
+
+
         c.showPage()
-        # full kuten chart in EUC
-        c.setFont('Helvetica', 24)
-        c.drawString(72,750, 'Characters available in JIS 0208-1997')
-        y = 600
-        for row in range(1, 95):
-            KutenRowCodeChart(row, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, y)
-            y = y - 125
-            if y < 50:
-                c.setFont('Helvetica',10)
-                c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber())
-                c.showPage()
-                y = 700
+
+        from reportlab.lib import textsplit
+        
+        c.setFont('HeiseiMin-W3', 14)
+        y = 700
+        c.drawString(70, y, 'cannot end line')
+        y -= 20
+        for group in textsplit.CANNOT_START_LINE:
+            c.drawString(70, y, group)
+            y -= 20
+            c.setFont('Helvetica',10)
+            c.drawString(70, y, ' '.join(map(lambda x: repr(x)[4:-1], group)))
+            c.setFont('HeiseiMin-W3', 14)
+            y -= 20
+
+
+
+        y -= 20            
+        c.drawString(70, y, 'cannot end line')
+        y -= 20
+        for group in textsplit.CANNOT_END_LINE:
+            c.drawString(70, y, group)
+            y -= 20
+            c.setFont('Helvetica',10)
+            c.drawString(70, y, ' '.join(map(lambda x: repr(x)[2:], group)))
+            c.setFont('HeiseiMin-W3', 14)
+            y -= 20
+
+        c.showPage()
+##        c.showPage()
+##
+##
+##        # full kuten chart in EUC
+##        c.setFont('Helvetica', 24)
+##        c.drawString(72,750, 'Characters available in JIS 0208-1997')
+##        y = 600
+##        for row in range(1, 95):
+##            KutenRowCodeChart(row, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, y)
+##            y = y - 125
+##            if y < 50:
+##                c.setFont('Helvetica',10)
+##                c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber())
+##                c.showPage()
+##                y = 700
+##
+##        c.showPage()
+
+
+        #try with Unicode truetype - Mincho for starters
+##        import time
+##        started = time.clock()
+##        c.showPage()
+##        c.setFont('Helvetica',16)
+##        c.drawString(100,750, 'About to say Tokyo in MS Gothic...')
+##
+##        from reportlab.pdfbase.ttfonts import TTFont, TTFontFile
+##        f = TTFontFile("msgothic.ttf")
+##        print f.name
+##        
+##        pdfmetrics.registerFont(TTFont(f.name, f))
+##        
+##        utfText = u'Andr\202'.encode('utf8')
+##        c.setFont(f.name,16)
+##        c.drawString(100,700, utfText)
+##
+##
+##        #tokyoUCS2 = '\x67\x71\x4E\xAC'
+##        finished = time.clock()
+        
+        
+
+
 
         c.save()
 
--- a/reportlab/test/test_pdfbase_encodings.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_pdfbase_encodings.py	Wed Apr 05 15:18:32 2006 +0000
@@ -3,10 +3,165 @@
 
 from reportlab.pdfgen.canvas import Canvas
 from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.ttfonts import TTFont
+from reportlab.pdfbase import pdfutils
+
+from reportlab.platypus.paragraph import Paragraph
+from reportlab.lib.styles import ParagraphStyle
+from reportlab.graphics.shapes import Drawing, String, Ellipse
+import re
+import codecs
+textPat = re.compile(r'\([^(]*\)')
+
+#test sentences
+testCp1252 = 'copyright %s trademark %s registered %s ReportLab! Ol%s!' % (chr(169), chr(153),chr(174), chr(0xe9))
+testUni = unicode(testCp1252, 'cp1252')
+testUTF8 = testUni.encode('utf-8')
+# expected result is octal-escaped text in the PDF
+expectedCp1252 = pdfutils._escape(testCp1252)
+
+def extractText(pdfOps):
+    """Utility to rip out the PDF text within a block of PDF operators.
+
+    PDF will show a string draw as something like "(Hello World) Tj"
+    i.e. text is in curved brackets. Crude and dirty, probably fails
+    on escaped brackets.
+    """
+    found = textPat.findall(pdfOps)
+    #chop off '(' and ')'
+    return map(lambda x:x[1:-1], found)
+
+def subsetToUnicode(ttf, subsetCodeStr):
+    """Return unicode string represented by given subsetCode string
+    as found when TrueType font rendered to PDF, ttf must be the font
+    object that was used."""
+    # This relies on TTFont internals and uses the first document
+    # and subset it finds
+    subset = ttf.state.values()[0].subsets[0]
+    chrs = []
+    for codeStr in subsetCodeStr.split('\\'):
+        if codeStr:
+            chrs.append(unichr(subset[int(codeStr[1:], 8)]))
+    return u''.join(chrs)
+
+class TextEncodingTestCase(unittest.TestCase):
+    """Tests of expected Unicode and encoding behaviour
+    """
+    def setUp(self):
+        self.luxi = TTFont("Luxi", "luxiserif.ttf")
+        pdfmetrics.registerFont(self.luxi)
+        self.styNormal = ParagraphStyle(name='Helvetica',  fontName='Helvetica-Oblique')
+        self.styTrueType = ParagraphStyle(name='TrueType',  fontName='luxi')
+
+    def testStringWidth(self):
+        msg = 'Hello World'
+        assert abs(pdfmetrics.stringWidth(msg, 'Courier', 10) - 66.0) < 0.01
+        assert abs(pdfmetrics.stringWidth(msg, 'Helvetica', 10) - 51.67) < 0.01
+        assert abs(pdfmetrics.stringWidth(msg, 'Times-Roman', 10) - 50.27) < 0.01
+        assert abs(pdfmetrics.stringWidth(msg, 'Luxi', 10) - 50.22) < 0.01
+
+        uniMsg1 = u"Hello World"
+        assert abs(pdfmetrics.stringWidth(uniMsg1, 'Courier', 10) - 66.0) < 0.01
+        assert abs(pdfmetrics.stringWidth(uniMsg1, 'Helvetica', 10) - 51.67) < 0.01
+        assert abs(pdfmetrics.stringWidth(uniMsg1, 'Times-Roman', 10) - 50.27) < 0.01
+        assert abs(pdfmetrics.stringWidth(uniMsg1, 'Luxi', 10) - 50.22) < 0.01
+
+
+        # Courier are all 600 ems wide.  So if one 'measures as utf8' one will
+        # get a wrong width as extra characters are seen
+        assert len(testCp1252) == 52
+        assert abs(pdfmetrics.stringWidth(testCp1252, 'Courier', 10, 'cp1252') - 312.0) < 0.01
+        # the test string has 5 more bytes and so "measures too long" if passed to
+        # a single-byte font which treats it as a single-byte string.
+        assert len(testUTF8)==57
+        assert abs(pdfmetrics.stringWidth(testUTF8, 'Courier', 10) - 312.0) < 0.01
+
+        assert len(testUni)==52
+        assert abs(pdfmetrics.stringWidth(testUni, 'Courier', 10) - 312.0) < 0.01
 
 
-class EncodingTestCase(unittest.TestCase):
-    "Make documents with custom encodings"
+        # now try a TrueType font.  Should be able to accept Unicode or UTF8
+        assert abs(pdfmetrics.stringWidth(testUTF8, 'Luxi', 10) - 224.44) < 0.01
+        assert abs(pdfmetrics.stringWidth(testUni, 'Luxi', 10) - 224.44) < 0.01
+
+    def testUtf8Canvas(self):
+        """Verify canvas declared as utf8 autoconverts.
+
+        This assumes utf8 input. It converts to the encoding of the
+        underlying font, so both text lines APPEAR the same."""
+
+
+        c = Canvas(outputfile('test_pdfbase_encodings_utf8.pdf'))
+
+        c.drawString(100,700, testUTF8)
+
+        # Set a font with UTF8 encoding
+        c.setFont('Luxi', 12)
+
+        # This should pass the UTF8 through unchanged
+        c.drawString(100,600, testUTF8)
+        # and this should convert from Unicode to UTF8
+        c.drawString(100,500, testUni)
+
+
+        # now add a paragraph in Latin-1 in the latin-1 style
+        p = Paragraph(testUTF8, style=self.styNormal, encoding="utf-8")
+        w, h = p.wrap(150, 100)
+        p.drawOn(c, 100, 400)  #3
+        c.rect(100,300,w,h)
+
+        # now add a paragraph in UTF-8 in the UTF-8 style
+        p2 = Paragraph(testUTF8, style=self.styTrueType, encoding="utf-8")
+        w, h = p2.wrap(150, 100)
+        p2.drawOn(c, 300, 400) #4
+        c.rect(100,300,w,h)
+
+        # now add a paragraph in Unicode in the latin-1 style
+        p3 = Paragraph(testUni, style=self.styNormal)
+        w, h = p3.wrap(150, 100)
+        p3.drawOn(c, 100, 300)
+        c.rect(100,300,w,h)
+
+        # now add a paragraph in Unicode in the UTF-8 style
+        p4 = Paragraph(testUni, style=self.styTrueType)
+        p4.wrap(150, 100)
+        p4.drawOn(c, 300, 300)
+        c.rect(300,300,w,h)
+
+        # now a graphic
+        d1 = Drawing(400,50)
+        d1.add(Ellipse(200,25,200,12.5, fillColor=None))
+        d1.add(String(200,25,testUTF8, textAnchor='middle', encoding='utf-8'))
+        d1.drawOn(c, 100, 150)
+
+        # now a graphic in utf8
+        d2 = Drawing(400,50)
+        d2.add(Ellipse(200,25,200,12.5, fillColor=None))
+        d2.add(String(200,25,testUTF8, fontName='Luxi', textAnchor='middle', encoding='utf-8'))
+        d2.drawOn(c, 100, 100)
+
+        # now a graphic in Unicode with T1 font
+        d3 = Drawing(400,50)
+        d3.add(Ellipse(200,25,200,12.5, fillColor=None))
+        d3.add(String(200,25,testUni, textAnchor='middle'))
+        d3.drawOn(c, 100, 50)
+
+        # now a graphic in Unicode with TT font
+        d4 = Drawing(400,50)
+        d4.add(Ellipse(200,25,200,12.5, fillColor=None))
+        d4.add(String(200,25,testUni, fontName='Luxi', textAnchor='middle'))
+        d4.drawOn(c, 100, 0)
+
+        extracted = extractText(c.getCurrentPageContent())
+        self.assertEquals(extracted[0], expectedCp1252)
+        self.assertEquals(extracted[1], extracted[2])
+        #self.assertEquals(subsetToUnicode(self.luxi, extracted[1]), testUni)
+        c.save()
+
+class FontEncodingTestCase(unittest.TestCase):
+    """Make documents with custom encodings of Type 1 built-in fonts.
+
+    Nothing really to do with character encodings; this is about hacking the font itself"""
 
     def test0(self):
         "Make custom encodings of standard fonts"
@@ -26,6 +181,7 @@
         pdfmetrics.registerEncoding(zenc)
 
         # now we can make a font based on this encoding
+        # AR hack/workaround: the name of the encoding must be a Python codec!
         f = pdfmetrics.Font('FontWithoutVowels', 'Helvetica-Oblique', 'EncodingWithoutVowels')
         pdfmetrics.registerFont(f)
 
@@ -86,11 +242,12 @@
 
         c.save()
 
-
+def makeSuite():
+    return makeSuiteForClasses(
+        TextEncodingTestCase,
 
-def makeSuite():
-    return makeSuiteForClasses(EncodingTestCase)
-
+        #FontEncodingTestCase - nobbled for now due to old stuff which needs removing.
+        )
 
 #noruntests
 if __name__ == "__main__":
--- a/reportlab/test/test_pdfbase_pdfmetrics.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_pdfbase_pdfmetrics.py	Wed Apr 05 15:18:32 2006 +0000
@@ -8,10 +8,8 @@
 The main test prints out a PDF documents enabling checking of widths of every
 glyph in every standard font.  Long!
 """
-
 from reportlab.test import unittest
 from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation
-
 from reportlab.pdfbase import pdfmetrics
 from reportlab.pdfbase import _fontdata
 from reportlab.pdfgen.canvas import Canvas
@@ -30,8 +28,9 @@
 def makeWidthTestForAllGlyphs(canv, fontName, outlining=1):
     """New page, then runs down doing all the glyphs in one encoding"""
     thisFont = pdfmetrics.getFont(fontName)
+    encName = thisFont.encName
     canv.setFont('Helvetica-Bold', 12)
-    title = 'Glyph Metrics Test for font %s, ascent=%s, descent=%s' % (fontName, str(thisFont.face.ascent), str(thisFont.face.descent))
+    title = 'Glyph Metrics Test for font %s, ascent=%s, descent=%s, encoding=%s' % (fontName, str(thisFont.face.ascent), str(thisFont.face.descent), encName)
     canv.drawString(80, 750,  title)
     canv.setFont('Helvetica-Oblique',10)
     canv.drawCentredString(297, 54, 'Page %d' % canv.getPageNumber())
@@ -53,14 +52,14 @@
         glyphName = glyphNames[i]
         if glyphName is not None:
             canv.setFont('Helvetica', 10)
-            canv.drawString(80, y, '%03d   %s ' % (i, glyphName))
+            text = unicode(chr(i),encName).encode('utf8')*30
             try:
-                glyphWidth = thisFont.face.glyphWidths[glyphName]
+                w = canv.stringWidth(text, fontName, 10)
+                canv.drawString(80, y, '%03d   %s w=%3d' % (i, glyphName, int((w/3.)*10)))
                 canv.setFont(fontName, 10)
-                canv.drawString(200, y, chr(i) * 30)
+                canv.drawString(200, y, text)
 
                 # now work out width and put a red marker next to the end.
-                w = canv.stringWidth(chr(i) * 30, fontName, 10)
                 canv.setFillColor(colors.red)
                 canv.rect(200 + w, y-1, 5, 10, stroke=0, fill=1)
                 canv.setFillColor(colors.black)
--- a/reportlab/test/test_pdfgen_general.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_pdfgen_general.py	Wed Apr 05 15:18:32 2006 +0000
@@ -205,6 +205,13 @@
     framePage(c, 'PDFgen graphics API test script')
     makesubsection(c, "PDFgen", 10*inch)
 
+    #quickie encoding test: when canvas encoding not set,
+    #the following should do (tm), (r) and (c)
+    c.drawString(100, 100, 'copyright %s trademark %s registered %s ReportLab!' % (unichr(169).encode('utf8'), unichr(153).encode('utf8'),unichr(174).encode('utf8')))
+
+    
+
+
     t = c.beginText(inch, 10*inch)
     t.setFont('Times-Roman', 10)
     drawCrossHairs(c, t.getX(),t.getY())
--- a/reportlab/test/test_pdfgen_links.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_pdfgen_links.py	Wed Apr 05 15:18:32 2006 +0000
@@ -49,22 +49,22 @@
         c.addOutlineEntry("Page 1","P1")
 
         #Note : XYZ Left is ignored because at this zoom the whole page fits the screen
-        c.bookmarkPage("P1_XYZ",fitType="XYZ",top=7*inch,left=3*inch,zoom=0.5)
+        c.bookmarkPage("P1_XYZ",fit="XYZ",top=7*inch,left=3*inch,zoom=0.5)
         c.addOutlineEntry("Page 1 XYZ #1 (top=7,left=3,zoom=0.5)","P1_XYZ",level=1)
 
-        c.bookmarkPage("P1_XYZ2",fitType="XYZ",top=7*inch,left=3*inch,zoom=5)
+        c.bookmarkPage("P1_XYZ2",fit="XYZ",top=7*inch,left=3*inch,zoom=5)
         c.addOutlineEntry("Page 1 XYZ #2 (top=7,left=3,zoom=5)","P1_XYZ2",level=1)
 
-        c.bookmarkPage("P1_FIT",fitType="Fit")
+        c.bookmarkPage("P1_FIT",fit="Fit")
         c.addOutlineEntry("Page 1 Fit","P1_FIT",level=1)
 
-        c.bookmarkPage("P1_FITH",fitType="FitH",top=2*inch)
+        c.bookmarkPage("P1_FITH",fit="FitH",top=2*inch)
         c.addOutlineEntry("Page 1 FitH (top = 2 inch)","P1_FITH",level=1)
 
-        c.bookmarkPage("P1_FITV",fitType="FitV",left=3*inch)
+        c.bookmarkPage("P1_FITV",fit="FitV",left=3*inch)
         c.addOutlineEntry("Page 1 FitV (left = 3 inch)","P1_FITV",level=1)
 
-        c.bookmarkPage("P1_FITR",fitType="FitR",left=1*inch,bottom=2*inch,right=5*inch,top=6*inch)
+        c.bookmarkPage("P1_FITR",fit="FitR",left=1*inch,bottom=2*inch,right=5*inch,top=6*inch)
         c.addOutlineEntry("Page 1 FitR (left=1,bottom=2,right=5,top=6)","P1_FITR",level=1)
 
         c.bookmarkPage("P1_FORWARD")
--- a/reportlab/test/test_platypus_tables.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_platypus_tables.py	Wed Apr 05 15:18:32 2006 +0000
@@ -27,7 +27,9 @@
     styles = []
     for i in range(5):
         styles.append(TableStyle([('ALIGN', (1,1), (-1,-1), 'RIGHT'),
-                                         ('ALIGN', (0,0), (-1,0), 'CENTRE') ]))
+                                  ('ALIGN', (0,0), (-1,0), 'CENTRE'),
+                                  ('HREF', (0,0), (0,0), 'www.google.com'),
+                                  ]))
     for style in styles[1:]:
         style.add('GRID', (0,0), (-1,-1), 0.25, colors.black)
     for style in styles[2:]:
--- a/reportlab/test/test_tools_pythonpoint.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/test/test_tools_pythonpoint.py	Wed Apr 05 15:18:32 2006 +0000
@@ -1,16 +1,12 @@
 """Tests for the PythonPoint tool.
 """
-
 import os, sys, string
 from reportlab.test import unittest
 from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation
-
 import reportlab
 
-
 class PythonPointTestCase(unittest.TestCase):
     "Some very crude tests on PythonPoint."
-
     def test0(self):
         "Test if pythonpoint.pdf can be created from pythonpoint.xml."
 
@@ -37,7 +33,6 @@
 def makeSuite():
     return makeSuiteForClasses(PythonPointTestCase)
 
-
 #noruntests
 if __name__ == "__main__":
     unittest.TextTestRunner().run(makeSuite())
--- a/reportlab/tools/docco/graphdocpy.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/tools/docco/graphdocpy.py	Wed Apr 05 15:18:32 2006 +0000
@@ -80,7 +80,7 @@
         canvas.setFont('Times-Roman', 12)
         canvas.drawString(4 * inch, cm, "%d" % pageNumber)
         if hasattr(canvas, 'headerLine'): # hackish
-            headerline = string.join(canvas.headerLine, ' \215 ')
+            headerline = string.join(canvas.headerLine, ' \xc2\x8d ')
             canvas.drawString(2*cm, A4[1]-1.75*cm, headerline)
 
     canvas.setFont('Times-Roman', 8)
--- a/reportlab/tools/docco/rl_doc_utils.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/tools/docco/rl_doc_utils.py	Wed Apr 05 15:18:32 2006 +0000
@@ -134,7 +134,7 @@
     getStory().append(P)
 
 def bullet(text):
-    text='<bullet><font name="Symbol">'+chr(183)+'</font></bullet>' + quickfix(text)
+    text='<bullet><font name="Symbol">\xe2\x80\xa2</font></bullet>' + quickfix(text)
     P = Paragraph(text, BU)
     getStory().append(P)
 
--- a/reportlab/tools/pythonpoint/pythonpoint.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/tools/pythonpoint/pythonpoint.py	Wed Apr 05 15:18:32 2006 +0000
@@ -951,13 +951,6 @@
     global _styles
     _styles = newStyleSheet
 
-
-##def test():
-##    p = stdparser.PPMLParser()
-##    p.feed(sample)
-##    p.getPresentation().save()
-##    p.close()
-
 _pyRXP_Parser = None
 def validate(rawdata):
     global _pyRXP_Parser
@@ -988,7 +981,6 @@
 
     #if pyRXP present, use it to check and get line numbers for errors...
     validate(rawdata)
-
     return _process(rawdata, datafilename, notes, handout, printout, cols, verbose, outDir, fx)
 
 def _process(rawdata, datafilename, notes=0, handout=0, printout=0, cols=0, verbose=0, outDir=None, fx=1):
--- a/reportlab/tools/pythonpoint/stdparser.py	Wed Mar 15 16:47:27 2006 +0000
+++ b/reportlab/tools/pythonpoint/stdparser.py	Wed Apr 05 15:18:32 2006 +0000
@@ -212,7 +212,6 @@
         self.fx = 1
         xmllib.XMLParser.__init__(self)
 
-
     def _arg(self,tag,args,name):
         "What's this for???"
         if args.has_key(name):
@@ -224,7 +223,6 @@
                 v = None
         return v
 
-
     def ceval(self,tag,args,name):
         if args.has_key(name):
             v = args[name]
@@ -240,7 +238,6 @@
 
         return eval(v)
 
-
     def getPresentation(self):
         return self._curPres
 
@@ -268,7 +265,6 @@
         elif self._curSubject <> None:
             self._curSubject = self._curSubject + data
 
-
     def handle_cdata(self, data):
         #just append to current paragraph text, so we can quote XML
         if self._curPara:
@@ -286,7 +282,6 @@
         elif self._curSubject <> None:
             self._curSubject = self._curSubject + data
 
-
     def start_presentation(self, args):
         self._curPres = pythonpoint.PPPresentation()
         self._curPres.filename = self._arg('presentation',args,'filename')
@@ -301,12 +296,10 @@
             self._curPres.pageWidth = w
         #print 'page size =', self._curPres.pageSize
 
-
     def end_presentation(self):
         pass
 ##        print 'Fully parsed presentation',self._curPres.filename
 
-
     def start_title(self, args):
         self._curTitle = ''
 
@@ -315,25 +308,20 @@
         self._curPres.title = self._curTitle
         self._curTitle = None
 
-
     def start_author(self, args):
         self._curAuthor = ''
 
-
     def end_author(self):
         self._curPres.author = self._curAuthor
         self._curAuthor = None
 
-
     def start_subject(self, args):
         self._curSubject = ''
 
-
     def end_subject(self):
         self._curPres.subject = self._curSubject
         self._curSubject = None
 
-
     def start_stylesheet(self, args):
         #makes it the current style sheet.
         path = self._arg('stylesheet',args,'path')
@@ -356,16 +344,13 @@
         pythonpoint.setStyles(func())
 ##        print 'set global stylesheet to %s.%s()' % (modulename, funcname)
 
-
     def end_stylesheet(self):
         pass
 
-
     def start_section(self, args):
         name = self._arg('section',args,'name')
         self._curSection = pythonpoint.PPSection(name)
 
-
     def end_section(self):
         self._curSection = None
 
@@ -399,12 +384,10 @@
         s.section = self._curSection
         self._curSlide = s
 
-
     def end_slide(self):
         self._curPres.slides.append(self._curSlide)
         self._curSlide = None
 
-
     def start_frame(self, args):
         self._curFrame = pythonpoint.PPFrame(
             self.ceval('frame',args,'x'),
@@ -415,22 +398,18 @@
         if self._arg('frame',args,'border')=='true':
             self._curFrame.showBoundary = 1
 
-
     def end_frame(self):
         self._curSlide.frames.append(self._curFrame)
         self._curFrame = None
 
-
     def start_notes(self, args):
         name = self._arg('notes',args,'name')
         self._curNotes = pythonpoint.PPNotes()
 
-
     def end_notes(self):
         self._curSlide.notes.append(self._curNotes)
         self._curNotes = None
 
-
     def start_registerFont(self, args):
         name = self._arg('font',args,'name')
         path = self._arg('font',args,'path')
@@ -466,15 +445,14 @@
         bt = self._arg('para',args,'bullettext')
         if bt == '':
             if self._curPara.style == 'Bullet':
-                bt = '\267'  # Symbol Font bullet character, reasonable default
+                bt = '\xc2\xb7'  # Symbol Font bullet character, reasonable default
             elif self._curPara.style == 'Bullet2':
-                bt = '\267'  # second-level bullet
+                bt = '\xc2\xb7'  # second-level bullet
             else:
                 bt = None
 
         self._curPara.bulletText = bt
 
-
     def end_para(self):
         if self._curFrame:
             self._curFrame.content.append(self._curPara)
@@ -737,7 +715,6 @@
         initargs = self.ceval('customshape',args,'initargs')
         self._curCustomShape = apply(func, initargs)
 
-
     def end_customshape(self):
         if self._curSlide:
             self._curSlide.graphics.append(self._curCustomShape)
@@ -745,16 +722,11 @@
             self._curSection.graphics.append(self._curCustomShape)
         self._curCustomShape = None
 
-
-
     def start_drawing(self, args):
         #loads one
-
         moduleName = args["module"]
         funcName = args["constructor"]
-
         showBoundary = int(args.get("showBoundary", "0"))
-
         hAlign = args.get("hAlign", "CENTER")
 
 
@@ -783,12 +755,10 @@
         self._curDrawing = pythonpoint.PPDrawing()
         self._curDrawing.drawing = drawing
 
-
     def end_drawing(self):
         self._curFrame.content.append(self._curDrawing)
         self._curDrawing = None
 
-
     def start_pageCatcherFigure(self, args):
         filename = args["filename"]
         pageNo = int(args["pageNo"])
@@ -809,8 +779,6 @@
         self._curFigure = pythonpoint.PPFigure()
         self._curFigure.figure = fig
 
-
-
     def end_pageCatcherFigure(self):
         self._curFrame.content.append(self._curFigure)
         self._curFigure = None
@@ -832,3 +800,14 @@
             self._curPara.rawtext = self._curPara.rawtext + '</%s>'% tag
         else:
             print 'Unknown end tag %s' % tag
+
+    def handle_charref(self, name):
+        try:
+            if name[0]=='x':
+                n = int(name[1:],16)
+            else:
+                n = int(name)
+        except ValueError:
+            self.unknown_charref(name)
+            return
+        self.handle_data(unichr(n).encode('utf8'))