reprotlab: inline images horizontal positioning OK
authorrgbecker
Mon, 01 Oct 2007 15:25:36 +0000
changeset 2857 487dc2450eec
parent 2856 2225ac76e622
child 2858 f7b81b16bf81
reprotlab: inline images horizontal positioning OK
reportlab/platypus/paragraph.py
reportlab/platypus/paraparser.py
reportlab/test/test_platypus_paragraphs.py
--- a/reportlab/platypus/paragraph.py	Fri Sep 28 12:52:01 2007 +0000
+++ b/reportlab/platypus/paragraph.py	Mon Oct 01 15:25:36 2007 +0000
@@ -102,10 +102,31 @@
     setXPos(tx,-offset)
     return offset
 
+def imgVRange(h,va,fontSize,leading):
+    '''return bottom,top offsets relative to baseline(0)'''
+    if va=='baseline':
+        iyo = 0
+    elif va in ('text-top','top'):
+        iyo = fontSize-h
+    elif va=='middle':
+        iyo = fontSize - (leading+h)*0.5
+    elif va in ('text-bottom','bottom'):
+        iyo = fontSize - leading
+    elif va=='super':
+        iyo = 0.5*fontSize
+    elif va=='sub':
+        iyo = -0.5*fontSize
+    elif hasattr(va,'normalizedValue'):
+        iyo = va.normalizedValue(fontSize)
+    else:
+        iyo = va
+    return iyo,iyo+h
+
 def _putFragLine(tx,line):
     xs = tx.XtraState
     cur_x = 0
     cur_y = xs.cur_y
+    x0 = tx._x0
     autoLeading = xs.autoLeading
     leading = xs.leading
     if autoLeading=='max':
@@ -119,22 +140,34 @@
         tx.setLeading(leading)
         if oleading!=None:
             cur_y -= (leading-oleading)/1.6
-            tx.setTextOrigin(tx._x0,cur_y)
+            tx.setTextOrigin(x0,cur_y)
             xs.cur_y = cur_y
     ws = getattr(tx,'_wordSpace',0)
     nSpaces = 0
     words = line.words
     for f in words:
         if hasattr(f,'cbDefn'):
-            name = f.cbDefn.name
-            kind = f.cbDefn.kind
-            if kind=='anchor':
-                tx._canvas.bookmarkHorizontal(name,cur_x,cur_y+leading)
+            cbDefn = f.cbDefn
+            kind = cbDefn.kind
+            if kind=='img':
+                #draw image cbDefn,cur_y,cur_x
+                w = cbDefn.width
+                h = cbDefn.height
+                iy0,iy1 = imgVRange(h,cbDefn.valign,tx._fontsize,tx._leading)
+                cur_x_s = cur_x + nSpaces*ws
+                tx._canvas.drawImage(cbDefn.image,cur_x_s,cur_y+iy0,w,h)
+                cur_x += w
+                cur_x_s += w
+                setXPos(tx,cur_x_s-tx._x0)
             else:
-                func = getattr(tx._canvas,name,None)
-                if not func:
-                    raise AttributeError, "Missing %s callback attribute '%s'" % (kind,name)
-                func(tx._canvas,kind,f.cbDefn.label)
+                name = cbDefn.name
+                if kind=='anchor':
+                    tx._canvas.bookmarkHorizontal(name,cur_x,cur_y+leading)
+                else:
+                    func = getattr(tx._canvas,name,None)
+                    if not func:
+                        raise AttributeError, "Missing %s callback attribute '%s'" % (kind,name)
+                    func(tx._canvas,kind,cbDefn.label)
             if f is words[-1]:
                 if not tx._fontname:
                     tx.setFont(xs.style.fontName,xs.style.fontSize)
@@ -181,8 +214,8 @@
                 xs.link = f.link
                 xs.link_x = cur_x_s
             elif xs.link and f.link is not xs.link:
-                    xs.links.append( (xs.link_x, cur_x_s, xs.link) )
-                    xs.link = None
+                xs.links.append( (xs.link_x, cur_x_s, xs.link) )
+                xs.link = None
             txtlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize)
             cur_x += txtlen
             nSpaces += text.count(' ')
@@ -193,6 +226,8 @@
         xs.strikes.append( (xs.strike_x, cur_x_s, xs.strikeColor) )
     if xs.link:
         xs.links.append( (xs.link_x, cur_x_s, xs.link) )
+    if tx._x0!=x0:
+        setXPos(tx,x0-tx._x0)
 
 def _leftDrawParaLineX( tx, offset, line, last=0):
     setXPos(tx,offset)
@@ -285,7 +320,16 @@
                 W = []
                 n = 0
         elif hasattr(f,'cbDefn'):
-            W.append((f,''))
+            w = getattr(f.cbDefn,'width',0)
+            if w:
+                if W!=[]:
+                    W.insert(0,n)
+                    R.append(W)
+                    W = []
+                    n = 0
+                R.append([w,(f,' ')])
+            else:
+                W.append((f,''))
         elif hasattr(f, 'lineBreak'):
             #pass the frag through.  The line breaker will scan for it.
             if W!=[]:
@@ -321,7 +365,7 @@
             f.append(w)
         if l is not lines[-1]:
             i = len(f)-1
-            while hasattr(f[i],'cbDefn'): i = i-1
+            while hasattr(f[i],'cbDefn') and not getattr(f[i].cbDefn,'width',0): i -= 1
             g = f[i]
             if g.text and g.text[-1]!=' ': g.text += ' '
     return f
@@ -361,7 +405,7 @@
         indent = style.leftIndent+style.firstLineIndent
         if bulletRight > indent:
             #..then it overruns, and we have less space available on line 1
-            maxWidths[0] = maxWidths[0] - (bulletRight - indent)
+            maxWidths[0] -= (bulletRight - indent)
 
 def splitLines0(frags,widths):
     '''
@@ -511,14 +555,14 @@
         <font name=fontfamily/fontname color=colorname size=float>
         <onDraw name=callable label="a label">
         <link>link text</link>
-            attributes of links 
+            attributes of links
                 size/fontSize=num
                 name/face/fontName=name
                 fg/textColor/color=color
                 backcolor/backColor/bgcolor=color
                 dest/destination/target/href/link=target
         <a>anchor text</a>
-            attributes of anchors 
+            attributes of anchors
                 fontSize=num
                 fontName=name
                 fg/textColor/color=color
@@ -527,6 +571,7 @@
         <a name="anchorpoint"/>
         <unichar name="unicode character name"/>
         <unichar value="unicode code point"/>
+        <img src="path" width="1in" height="1in" valign="bottom"/>
 
         The whole may be surrounded by <para> </para> tags
 
@@ -820,7 +865,7 @@
                         if currentWidth>0 and ((nText!='' and nText[0]!=' ') or hasattr(f,'cbDefn')):
                             if hasattr(g,'cbDefn'):
                                 i = len(words)-1
-                                while hasattr(words[i],'cbDefn'): i -= 1
+                                while hasattr(words[i],'cbDefn') and not getattr(words[i].cbDefn,'width',0): i -= 1
                                 words[i].text += ' '
                             else:
                                 g.text += ' '
@@ -959,7 +1004,7 @@
             bw = getattr(style,'borderWidth',None)
             bc = getattr(style,'borderColor',None)
             bg = style.backColor
-        
+
         #if has a background or border, draw it
         if bg or (bc and bw):
             canvas.saveState()
@@ -1156,7 +1201,7 @@
             if hasattr(w[0],a):
                 R.append('%s=%r' % (a,getattr(w[0],a)))
         return ', '.join(R)
-        
+
     def dumpParagraphFrags(P):
         print 'dumpParagraphFrags(<Paragraph @ %d>) minWidth() = %.2f' % (id(P), P.minWidth())
         frags = P.frags
@@ -1288,7 +1333,7 @@
 
     if flagged(9):
         text="""Furthermore, the fundamental error of
-regarding functional notions as
+regarding <img src="../docs/images/testimg.gif" width="3" height="7"/> functional notions as
 categorial delimits a general
 convention regarding the forms of the<br/>
 grammar. I suggested that these results
--- a/reportlab/platypus/paraparser.py	Fri Sep 28 12:52:01 2007 +0000
+++ b/reportlab/platypus/paraparser.py	Mon Oct 01 15:25:36 2007 +0000
@@ -11,6 +11,7 @@
 import unicodedata
 import reportlab.lib.sequencer
 from reportlab.lib.abag import ABag
+from reportlab.lib.utils import ImageReader
 
 from reportlab.lib import xmllib
 
@@ -24,6 +25,19 @@
 subFraction = 0.5   # fraction of font size that a sub script should be lowered
 superFraction = 0.5 # fraction of font size that a super script should be raised
 
+
+def _convnum(s, unit=1):
+    if s[0] in ['+','-']:
+        try:
+            return ('relative',int(s)*unit)
+        except ValueError:
+            return ('relative',float(s)*unit)
+    else:
+        try:
+            return int(s)*unit
+        except ValueError:
+            return float(s)*unit
+
 def _num(s, unit=1):
     """Convert a string like '10cm' to an int or float (in points).
        The default unit is point, but optionally you can use other
@@ -47,16 +61,28 @@
     if s[-4:]=='pica':
         unit=pica
         s = s[:-4]
-    if s[0] in ['+','-']:
-        try:
-            return ('relative',int(s)*unit)
-        except ValueError:
-            return ('relative',float(s)*unit)
-    else:
-        try:
-            return int(s)*unit
-        except ValueError:
-            return float(s)*unit
+    return _convnum(s,unit)
+
+class _PCT:
+    def __init__(self,v):
+        self._value = v*0.01
+
+    def normalizedValue(self,normalizer):
+        return normalizer*self._value
+
+def _valignpc(s):
+    s = s.lower()
+    if s in ('baseline','sub','super','top','text-top','middle','bottom','text-bottom'):
+        return s
+    if s.endswith('%'):
+        n = _convnum(s[:-1])
+        if isinstance(n,tuple):
+            n = n[1]
+        return _PCT(n)
+    n = _num(s)
+    if isinstance(n,tuple):
+        n = n[1]
+    return n
 
 def _autoLeading(x):
     x = x.lower()
@@ -136,6 +162,12 @@
                 'bgcolor':('backColor',toColor),
                 'href': ('href', None),
                 }
+_imgAttrMap = {
+                'src': ('src', None),
+                'width': ('width',_num),
+                'height':('height',_num),
+                'valign':('valign',_valignpc),
+                }
 
 def _addAttributeNames(m):
     K = m.keys()
@@ -354,6 +386,7 @@
 #       <a name="anchorpoint"/>
 #       <unichar name="unicode character name"/>
 #       <unichar value="unicode code point"/>
+#       <img src="path" width="1in" height="1in" valign="bottom"/>
 #       <greek> - </greek>
 #
 #       The whole may be surrounded by <para> </para> tags
@@ -461,6 +494,28 @@
             del self._stack[-1]
             assert frag.link!=None
 
+    def start_img(self,attributes):
+        A = self.getAttributes(attributes,_imgAttrMap)
+        if not A.get('src'):
+            self._syntax_error('<img> needs src attribute')
+        A['_selfClosingTag'] = 'img'
+        self._push(**A)
+
+    def end_img(self):
+        frag = self._stack[-1]
+        assert getattr(frag,'_selfClosingTag',''),'Parser failure in <img/>'
+        defn = frag.cbDefn = ABag()
+        defn.kind = 'img'
+        defn.src = getattr(frag,'src',None)
+        defn.image = ImageReader(defn.src)
+        size = defn.image.getSize()
+        defn.width = getattr(frag,'width',size[0])
+        defn.height = getattr(frag,'height',size[1])
+        defn.valign = getattr(frag,'valign','bottom')
+        del frag._selfClosingTag
+        self.handle_data('')
+        self._pop()
+
     #### super script
     def start_super( self, attributes ):
         self._push(super=1)
@@ -750,7 +805,8 @@
 
         frag = copy.copy(self._stack[-1])
         if hasattr(frag,'cbDefn'):
-            if data!='': self._syntax_error('Only <onDraw> tag allowed')
+            kind = frag.cbDefn.kind
+            if data: self._syntax_error('Only empty <%s> tag allowed' % kind)
         elif hasattr(frag,'_selfClosingTag'):
             if data!='': self._syntax_error('No content allowed in %s tag' % frag._selfClosingTag)
             return
@@ -875,7 +931,7 @@
             for l in rv:
                 print l.fontName,l.fontSize,l.textColor,l.bold, l.rise, '|%s|'%l.text[:25],
                 if hasattr(l,'cbDefn'):
-                    print 'cbDefn',l.cbDefn.name,l.cbDefn.label,l.cbDefn.kind
+                    print 'cbDefn',getattr(l.cbDefn,'name',''),getattr(l.cbDefn,'label',''),l.cbDefn.kind
                 else: print
 
     style=ParaFrag()
@@ -991,3 +1047,4 @@
     check_text('''Here comes <font face="Helvetica" size="14pt">Helvetica 14</font> with <Strong>strong</Strong> <em>emphasis</em>.''')
     check_text('''Here comes <font face="Courier" size="3cm">Courier 3cm</font> and normal again.''')
     check_text('''Before the break <br/>the middle line <br/> and the last line.''')
+    check_text('''This should be an inline image <img src='../docs/images/testimg.gif'/>!''')
--- a/reportlab/test/test_platypus_paragraphs.py	Fri Sep 28 12:52:01 2007 +0000
+++ b/reportlab/test/test_platypus_paragraphs.py	Mon Oct 01 15:25:36 2007 +0000
@@ -313,19 +313,19 @@
         normal_sp = ParagraphStyle(name='normal_sp',parent=normal,alignment=TA_JUSTIFY,spaceBefore=12)
         texts = ['''Furthermore, a subset of <font size="14">English sentences</font> interesting on quite
 independent grounds is not quite equivalent to a stipulation to place
-the constructions into these various categories.''',
+<font color="blue">the constructions <img src="../docs/images/testimg.gif"/> into these various categories.</font>''',
         '''We will bring <font size="18">Ugly Things</font> in favor of
 The following thesis:  most of the methodological work in Modern
-Linguistics can be defined in such a way as to impose problems of
-phonemic and <u>morphological</u> analysis.''']
+Linguistics can be <img src="../docs/images/testimg.gif" valign="baseline" /> defined in such <img src="../docs/images/testimg.gif" valign="10" /> a way as to impose problems of
+phonemic and <u>morphological <img src="../docs/images/testimg.gif" valign="top"/> </u> analysis.''']
         story =[]
         a = story.append
         t = 'u'
         n = 1
-        s = normal
-        for autoLeading in ('','min','max'):
-            a(Paragraph('style=%s(autoLeading=%s)'%(s.name,autoLeading),style=normal_sp))
-            a(Paragraph('<para autoleading="%s"><%s>%s</%s>. %s <%s>%s</%s>. %s</para>' % (
+        for s in (normal,normal_sp):
+            for autoLeading in ('','min','max'):
+                a(Paragraph('style=%s(autoLeading=%s)'%(s.name,autoLeading),style=normal_sp))
+                a(Paragraph('<para autoleading="%s"><%s>%s</%s>. %s <%s>%s</%s>. %s</para>' % (
                             autoLeading,
                             t,' '.join((n+1)*['A']),t,texts[0],t,' '.join((n+1)*['A']),t,texts[1]),
                             style=s))