add justifyBreaks & justifyLastLine ParagraphStyle attributes; version-->3.2.7
authorrobin
Fri, 31 Jul 2015 16:43:25 +0100
changeset 4220 c0e82d246798
parent 4219 e42c9f82f08d
child 4221 c966db9ea2f2
add justifyBreaks & justifyLastLine ParagraphStyle attributes; version-->3.2.7
src/reportlab/__init__.py
src/reportlab/lib/styles.py
src/reportlab/platypus/paragraph.py
src/reportlab/platypus/paraparser.py
tests/test_platypus_paragraphs.py
--- a/src/reportlab/__init__.py	Tue Jul 28 13:45:40 2015 +0100
+++ b/src/reportlab/__init__.py	Fri Jul 31 16:43:25 2015 +0100
@@ -1,9 +1,9 @@
 #Copyright ReportLab Europe Ltd. 2000-2015
 #see license.txt for license details
 __doc__="""The Reportlab PDF generation library."""
-Version = "3.2.6"
+Version = "3.2.7"
 __version__=Version
-__date__='20150728'
+__date__='20150731'
 
 import sys, os, imp
 
--- a/src/reportlab/lib/styles.py	Tue Jul 28 13:45:40 2015 +0100
+++ b/src/reportlab/lib/styles.py	Fri Jul 31 16:43:25 2015 +0100
@@ -130,6 +130,8 @@
         'splitLongWords':1,     #make best efforts to split long words
         'underlineProportion': _baseUnderlineProportion,    #set to non-zero to get proportional
         'bulletAnchor': 'start',    #where the bullet is anchored ie start, middle, end or numeric
+        'justifyLastLine': 0,   #n allow justification on the last line for more than n words 0 means don't bother
+        'justifyBreaks': 0,     #justify lines broken with <br/>
         }
 
 class LineStyle(PropertySet):
--- a/src/reportlab/platypus/paragraph.py	Tue Jul 28 13:45:40 2015 +0100
+++ b/src/reportlab/platypus/paragraph.py	Fri Jul 31 16:43:25 2015 +0100
@@ -1329,6 +1329,7 @@
                 #preserving splitting algorithm
                 return self.blPara
             n = 0
+            njlbv = not style.justifyBreaks
             words = []
             _words = _getFragWords(frags,maxWidth)
             while _words:
@@ -1437,7 +1438,7 @@
                     if currentWidth>self.width: self.width = currentWidth
                     #end of line
                     lines.append(FragLine(extraSpace=maxWidth-currentWidth, wordCount=n,
-                                        lineBreak=lineBreak, words=words, fontSize=maxSize, ascent=maxAscent, descent=minDescent, maxWidth=maxWidth))
+                                        lineBreak=lineBreak and njlbv, words=words, fontSize=maxSize, ascent=maxAscent, descent=minDescent, maxWidth=maxWidth))
 
                     #start new line
                     lineno += 1
@@ -1483,7 +1484,7 @@
             #deal with any leftovers on the final line
             if words!=[]:
                 if currentWidth>self.width: self.width = currentWidth
-                lines.append(ParaLines(extraSpace=(maxWidth - currentWidth),wordCount=n,
+                lines.append(ParaLines(extraSpace=(maxWidth - currentWidth),wordCount=n,lineBreak=False,
                                     words=words, fontSize=maxSize,ascent=maxAscent,descent=minDescent,maxWidth=maxWidth))
             return ParaLines(kind=1, lines=lines)
 
@@ -1597,7 +1598,8 @@
             alignment = style.alignment
             offset = style.firstLineIndent+_offsets[0]
             lim = nLines-1
-            noJustifyLast = not (hasattr(self,'_JustifyLast') and self._JustifyLast)
+            noJustifyLast = not getattr(self,'_JustifyLast',False)
+            jllwc = style.justifyLastLine
 
             if blPara.kind==0:
                 if alignment == TA_LEFT:
@@ -1637,7 +1639,11 @@
                 #now the font for the rest of the paragraph
                 tx.setFont(f.fontName, f.fontSize, leading)
                 ws = lines[0][0]
-                t_off = dpl( tx, offset, ws, lines[0][1], noJustifyLast and nLines==1)
+                words = lines[0][1]
+                lastLine = noJustifyLast and nLines==1
+                if lastLine and jllwc and len(words)>jllwc:
+                    lastLine=False
+                t_off = dpl( tx, offset, ws, words, lastLine)
                 if f.underline or f.link or f.strike or style.endDots:
                     xs = tx.XtraState = ABag()
                     xs.cur_y = cur_y
@@ -1661,21 +1667,29 @@
                     if underline: _do_under_line(0, dx, ws, tx)
                     if strike: _do_under_line(0, dx, ws, tx, lm=0.125)
                     if link: _do_link_line(0, dx, ws, tx)
-                    if noJustifyLast and nLines==1 and style.endDots and dpl!=_rightDrawParaLine: _do_dots(0, dx, ws, xs, tx, dpl)
+                    if lastLine and style.endDots and dpl!=_rightDrawParaLine: _do_dots(0, dx, ws, xs, tx, dpl)
 
                     #now the middle of the paragraph, aligned with the left margin which is our origin.
                     for i in xrange(1, nLines):
                         ws = lines[i][0]
-                        t_off = dpl( tx, _offsets[i], ws, lines[i][1], noJustifyLast and i==lim)
+                        words = lines[i][1]
+                        lastLine = noJustifyLast and i==lim
+                        if lastLine and jllwc and len(words)>jllwc:
+                            lastLine=False
+                        t_off = dpl( tx, _offsets[i], ws, words, lastLine)
                         dx = t_off+leftIndent
                         if dpl!=_justifyDrawParaLine: ws = 0
                         if underline: _do_under_line(i, dx, ws, tx)
                         if strike: _do_under_line(i, dx, ws, tx, lm=0.125)
                         if link: _do_link_line(i, dx, ws, tx)
-                        if noJustifyLast and i==lim and style.endDots and dpl!=_rightDrawParaLine: _do_dots(i, dx, ws, xs, tx, dpl)
+                        if lastLine and style.endDots and dpl!=_rightDrawParaLine: _do_dots(i, dx, ws, xs, tx, dpl)
                 else:
                     for i in xrange(1, nLines):
-                        dpl( tx, _offsets[i], lines[i][0], lines[i][1], noJustifyLast and i==lim)
+                        words = lines[i][1]
+                        lastLine = noJustifyLast and i==lim
+                        if lastLine and jllwc and len(words)>jllwc:
+                            lastLine=False
+                        dpl( tx, _offsets[i], lines[i][0], words, lastLine)
             else:
                 if self.style.wordWrap == 'RTL':
                     for line in lines:
@@ -1735,13 +1749,20 @@
                 xs.autoLeading = autoLeading
 
                 tx._fontname,tx._fontsize = None, None
-                dpl( tx, offset, lines[0], noJustifyLast and nLines==1)
+                line = lines[0]
+                lastLine = noJustifyLast and nLines==1
+                if lastLine and jllwc and line.wordCount>jllwc:
+                    lastLine=False
+                dpl( tx, offset, line, lastLine)
                 _do_post_text(tx)
 
                 #now the middle of the paragraph, aligned with the left margin which is our origin.
                 for i in xrange(1, nLines):
-                    f = lines[i]
-                    dpl( tx, _offsets[i], f, noJustifyLast and i==lim)
+                    line = lines[i]
+                    lastLine = noJustifyLast and i==lim
+                    if lastLine and jllwc and line.wordCount>jllwc:
+                        lastLine=False
+                    dpl( tx, _offsets[i], line, lastLine)
                     _do_post_text(tx)
 
             canvas.drawText(tx)
--- a/src/reportlab/platypus/paraparser.py	Tue Jul 28 13:45:40 2015 +0100
+++ b/src/reportlab/platypus/paraparser.py	Fri Jul 31 16:43:25 2015 +0100
@@ -14,7 +14,7 @@
 import reportlab.lib.sequencer
 
 from reportlab.lib.abag import ABag
-from reportlab.lib.utils import ImageReader, isPy3, annotateException, encode_label, asUnicode, asBytes, uniChr
+from reportlab.lib.utils import ImageReader, isPy3, annotateException, encode_label, asUnicode, asBytes, uniChr, isStr
 from reportlab.lib.colors import toColor, white, black, red, Color
 from reportlab.lib.fonts import tt2ps, ps2tt
 from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
@@ -71,6 +71,20 @@
         s = s[:-4]
     return _convnum(s,unit,allowRelative)
 
+def _int(s):
+    try:
+        return int(s)
+    except:
+        raise ValueError('cannot convert %r to int' % s)
+
+def _bool(s):
+    s = s.lower()
+    if s in ('true','1','yes'):
+        return True
+    if s in ('false','0','no'):
+        return False
+    raise ValueError('cannot convert %r to bool value' % s)
+
 def _numpct(s,unit=1,allowRelative=False):
     if s.endswith('%'):
         return _PCT(_convnum(s[:-1],allowRelative=allowRelative))
@@ -119,6 +133,20 @@
         raise ValueError('illegal bullet anchor %r' % s)
     return s
 
+def _wordWrapConv(s):
+    s = s.upper().strip()
+    if not s: return None
+    if s not in ('CJK','RTL','LTR'):
+        raise ValueError('cannot convert wordWrap=%r' % s)
+    return s
+
+def _textTransformConv(s):
+    s = s.lower().strip()
+    if not s: return None
+    if s not in ('uppercase','lowercase','capitalize','none'):
+        raise ValueError('cannot convert wordWrap=%r' % s)
+    return s
+
 _paraAttrMap = {'font': ('fontName', None),
                 'face': ('fontName', None),
                 'fontsize': ('fontSize', _num),
@@ -142,6 +170,19 @@
                 'bgcolor':('backColor',toColor),
                 'bg':('backColor',toColor),
                 'fg': ('textColor',toColor),
+                'justifybreaks': ('justifyBreaks',_bool),
+                'justifylastline': ('justifyLastLine',_int),
+                'wordwrap': ('wordWrap',_wordWrapConv),
+                'allowwidows': ('allowWidows',_bool),
+                'alloworphans': ('allowOrphans',_bool),
+                'splitlongwords': ('splitLongWords',_bool),
+                'borderwidth': ('borderWidth',_num),
+                'borderpadding': ('borderpadding',_num),
+                'bordercolor': ('borderColor',toColor),
+                'borderradius': ('borderRadius',_num),
+                'texttransform':('textTransform',_textTransformConv),
+                'enddots':('endDots',None),
+                'underlineproportion':('underlineProportion',_num),
                 }
 
 _bulletAttrMap = {
@@ -1010,7 +1051,7 @@
         for k, v in attr.items():
             if not self.caseSensitive:
                 k = k.lower()
-            if k in list(attrMap.keys()):
+            if k in attrMap:
                 j = attrMap[k]
                 func = j[1]
                 try:
@@ -1018,6 +1059,7 @@
                 except:
                     self._syntax_error('%s: invalid value %s'%(k,v))
             else:
+                raise ValueError('invalid attribute name %s attrMap=%r'% (k,list(sorted(attrMap.keys()))))
                 self._syntax_error('invalid attribute name %s'%k)
         return A
 
--- a/tests/test_platypus_paragraphs.py	Tue Jul 28 13:45:40 2015 +0100
+++ b/tests/test_platypus_paragraphs.py	Fri Jul 31 16:43:25 2015 +0100
@@ -583,8 +583,9 @@
 phonemic and morphological analysis.'''
         story =[]
         a = story.append
-        for mode in (0,1,2,3,4):
+        for mode in (0,1,2,3,4,5,6,7):
             text = text0
+            paraStyle = normal_just
             if mode==1:
                 text = text.replace('English sentences','<b>English sentences</b>').replace('quite equivalent','<i>quite equivalent</i>')
                 text = text.replace('the methodological work','<b>the methodological work</b>').replace('to impose problems','<i>to impose problems</i>')
@@ -599,10 +600,26 @@
                 text = text.replace('English ','English&nbsp;').replace('quite ','quite&nbsp;')
                 text = text.replace(' methodological','&nbsp;methodological').replace(' impose','&nbsp;impose')
                 a(Paragraph('Justified paragraph in normal font &amp; some hard spaces',style=normal))
+            elif mode in (5,6,7):
+                text = text.replace('as to impose','<br/>as to impose').replace(' most of the','<br/>most of the')
+                text = text.replace(' grounds','<br/>grounds').replace(' various','<br/>various')
+                if mode in (6,7):
+                    msg = []
+                    msg.append('justifyBreaks=1')
+                    paraStyle = paraStyle.clone('paraStyle6',paraStyle,justifyBreaks=1)
+                    if mode==7:
+                        msg.append('justifyLastLine=3')
+                        paraStyle = paraStyle.clone('paraStyle7',paraStyle,justifyLastLine=3)
+                    msg = '(%s) ' % (' '.join(msg))
+                else:
+                    a(PageBreak())
+                    msg = ' '
+
+                a(Paragraph('Justified%swith some &lt;br/&gt; tags' % msg,style=normal))
             else:
                 a(Paragraph('Justified paragraph in normal font',style=normal))
 
-            a(Paragraph(text,style=normal_just))
+            a(Paragraph(text,style=paraStyle))
         doc = MyDocTemplate(outputfile('test_platypus_paragraphs_just.pdf'))
         doc.build(story)