more controllable under and strike lines; fix for issue 137 contributed by Tom Alexander @ bitbucket; version-->3.4.30
--- a/src/reportlab/__init__.py Thu Mar 22 10:18:32 2018 +0000
+++ b/src/reportlab/__init__.py Fri Mar 23 16:02:08 2018 +0000
@@ -1,9 +1,9 @@
#Copyright ReportLab Europe Ltd. 2000-2017
#see license.txt for license details
__doc__="""The Reportlab PDF generation library."""
-Version = "3.4.29"
+Version = "3.4.30"
__version__=Version
-__date__='20180322'
+__date__='20180323'
import sys, os
--- a/src/reportlab/lib/rl_accel.py Thu Mar 22 10:18:32 2018 +0000
+++ b/src/reportlab/lib/rl_accel.py Fri Mar 23 16:02:08 2018 +0000
@@ -320,7 +320,7 @@
'returns 1 if two ParaFrags map out the same'
if (hasattr(f,'cbDefn') or hasattr(g,'cbDefn')
or hasattr(f,'lineBreak') or hasattr(g,'lineBreak')): return 0
- for a in ('fontName', 'fontSize', 'textColor', 'rise', 'underline', 'strike', 'link', "backColor"):
+ for a in ('fontName', 'fontSize', 'textColor', 'rise', 'us_lines', 'link', "backColor"):
if getattr(f,a,None)!=getattr(g,a,None): return 0
return 1
_py_funcs['sameFrag'] = sameFrag
--- a/src/reportlab/lib/styles.py Thu Mar 22 10:18:32 2018 +0000
+++ b/src/reportlab/lib/styles.py Fri Mar 23 16:02:08 2018 +0000
@@ -26,8 +26,13 @@
from reportlab.lib.enums import TA_LEFT, TA_CENTER
from reportlab.lib.fonts import tt2ps
from reportlab.rl_config import canvas_basefontname as _baseFontName, \
- baseUnderlineProportion as _baseUnderlineProportion, \
- spaceShrinkage
+ underlineWidth as _baseUnderlineWidth, \
+ underlineOffset as _baseUnderlineOffset, \
+ underlineGap as _baseUnderlineGap, \
+ strikeWidth as _baseStrikeWidth, \
+ strikeOffset as _baseStrikeOffset, \
+ strikeGap as _baseStrikeGap, \
+ spaceShrinkage, platypus_link_underline
_baseFontNameB = tt2ps(_baseFontName,1,0)
_baseFontNameI = tt2ps(_baseFontName,0,1)
_baseFontNameBI = tt2ps(_baseFontName,1,1)
@@ -130,11 +135,19 @@
#string or object with text and optional fontName, fontSize, textColor & backColor
#dy
'splitLongWords':1, #make best efforts to split long words
- 'underlineProportion': _baseUnderlineProportion, #set to non-zero to get proportional
+ 'underlineWidth': _baseUnderlineWidth, #underline width
'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/>
'spaceShrinkage': spaceShrinkage, #allow shrinkage of percentage of space to fit on line
+ 'strikeWidth': _baseStrikeWidth, #stroke width
+ 'underlineOffset': _baseUnderlineOffset, #fraction of fontsize to offset underlines
+ 'underlineGap': _baseUnderlineGap, #gap for double/triple underline
+ 'strikeOffset': _baseStrikeOffset, #fraction of fontsize to offset strikethrough
+ 'strikeGap': _baseStrikeGap, #gap for double/triple strike
+ 'linkUnderline': platypus_link_underline,
+ #'underlineColor': None,
+ #'strikeColor': None,
}
class LineStyle(PropertySet):
--- a/src/reportlab/platypus/paragraph.py Thu Mar 22 10:18:32 2018 +0000
+++ b/src/reportlab/platypus/paragraph.py Fri Mar 23 16:02:08 2018 +0000
@@ -7,7 +7,7 @@
from operator import truth
from unicodedata import category
from reportlab.pdfbase.pdfmetrics import stringWidth, getFont, getAscentDescent
-from reportlab.platypus.paraparser import ParaParser, _PCT
+from reportlab.platypus.paraparser import ParaParser, _PCT, _num as _parser_num, _re_us_value
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
@@ -15,8 +15,9 @@
from reportlab.lib.textsplit import wordSplit, ALL_CANNOT_START
from copy import deepcopy
from reportlab.lib.abag import ABag
-from reportlab.rl_config import platypus_link_underline, decimalSymbol, _FUZZ, paraFontSizeHeightOffset
-from reportlab.lib.utils import _className, isBytes, unicodeT, bytesT, strTypes
+from reportlab.rl_config import platypus_link_underline, decimalSymbol, _FUZZ, paraFontSizeHeightOffset, \
+ strikeGap, underlineGap
+from reportlab.lib.utils import _className, isBytes, unicodeT, bytesT, isStr
from reportlab.lib.rl_accel import sameFrag
from reportlab import xrange
import re
@@ -60,6 +61,20 @@
_wsc_re_split=re.compile('[%s]+'% re.escape(_wsc)).split
_wsc_end_search=re.compile('[%s]+$'% re.escape(_wsc)).search
+def _usConv(s, vMap, default=None):
+ '''convert a strike/underline distance to a number'''
+ if isStr(s):
+ s = s.strip()
+ if s:
+ m = _re_us_value.match(s)
+ if m:
+ return float(m.group(1))*vMap[m.group(2)]
+ else:
+ return _parser_num(s,allowRelative=False)
+ elif default:
+ return default
+ return s
+
def split(text, delim=None):
if isBytes(text): text = text.decode('utf8')
if delim is not None and isBytes(delim): delim = delim.decode('utf8')
@@ -176,7 +191,7 @@
def _getDotsInfo(style):
dots = style.endDots
- if isinstance(dots,strTypes):
+ if isStr(dots):
text = dots
fontName = style.fontName
fontSize = style.fontSize
@@ -227,6 +242,10 @@
ws = getattr(tx,'_wordSpace',0)
nSpaces = 0
words = line.words
+ AL = []
+ LL = []
+ us_lines = xs.us_lines
+ links = xs.links
for i, f in enumerate(words):
if hasattr(f,'cbDefn'):
cbDefn = f.cbDefn
@@ -264,59 +283,55 @@
else:
cur_x_s = cur_x + nSpaces*ws
end_x = cur_x_s
+ fontSize = f.fontSize
+ textColor = f.textColor
+ rise = f.rise
if i > 0:
end_x = cur_x_s - _trailingSpaceLength(words[i-1].text, tx)
- if (tx._fontname,tx._fontsize)!=(f.fontName,f.fontSize):
- tx._setFont(f.fontName, f.fontSize)
- if xs.textColor!=f.textColor:
- xs.textColor = f.textColor
- tx.setFillColor(f.textColor)
- if xs.rise!=f.rise:
- xs.rise=f.rise
- tx.setRise(f.rise)
+ if (tx._fontname,tx._fontsize)!=(f.fontName,fontSize):
+ tx._setFont(f.fontName, fontSize)
+ if xs.textColor!=textColor:
+ xs.textColor = textColor
+ tx.setFillColor(textColor)
+ if xs.rise!=rise:
+ xs.rise=rise
+ tx.setRise(rise)
text = f.text
tx._textOut(text,f is words[-1]) # cheap textOut
- if not xs.underline and f.underline:
- xs.underline = 1
- xs.underline_x = cur_x_s
- xs.underlineColor = f.textColor
- elif xs.underline:
- if not f.underline:
- xs.underline = 0
- xs.underlines.append( (xs.underline_x, end_x, xs.underlineColor) )
- xs.underlineColor = None
- elif xs.textColor!=xs.underlineColor:
- xs.underlines.append( (xs.underline_x, end_x, xs.underlineColor) )
- xs.underlineColor = xs.textColor
- xs.underline_x = cur_x_s
- if not xs.strike and f.strike:
- xs.strike = 1
- xs.strike_x = cur_x_s
- xs.strikeColor = f.textColor
- elif xs.strike:
- if not f.strike:
- xs.strike = 0
- xs.strikes.append( (xs.strike_x, end_x, xs.strikeColor) )
- xs.strikeColor = None
- elif xs.textColor!=xs.strikeColor:
- xs.strikes.append( (xs.strike_x, end_x, xs.strikeColor) )
- xs.strikeColor = xs.textColor
- xs.strike_x = cur_x_s
- if f.link and not xs.link:
- if not xs.link:
- xs.link = f.link
- xs.link_x = cur_x_s
- xs.linkColor = xs.textColor
- elif xs.link:
- if not f.link:
- xs.links.append( (xs.link_x, end_x, xs.link, xs.linkColor) )
- xs.link = None
- xs.linkColor = None
- elif f.link!=xs.link or xs.textColor!=xs.linkColor:
- xs.links.append( (xs.link_x, end_x, xs.link, xs.linkColor) )
- xs.link = f.link
- xs.link_x = cur_x_s
- xs.linkColor = xs.textColor
+ if LL != f.us_lines:
+ S = set(LL)
+ NS = set(f.us_lines)
+ nL = NS - S #new lines
+ eL = S - NS #ending lines
+ for l in eL:
+ us_lines[l] = us_lines[l],end_x
+ for l in nL:
+ us_lines[l] = (l,fontSize,textColor,cur_x),fontSize
+ LL = f.us_lines
+ if LL:
+ for l in LL:
+ l0, fsmax = us_lines[l]
+ if fontSize>fsmax:
+ us_lines[l] = l0, fontSize
+
+ nlo = rise - 0.2*fontSize
+ nhi = rise + fontSize
+ if AL != f.link:
+ S = set(AL)
+ NS = set(f.link)
+ nL = NS - S #new linkis
+ eL = S - NS #ending links
+ for l in eL:
+ links[l] = links[l],end_x
+ for l in nL:
+ links[l] = (l,cur_x),nlo,nhi
+ AL = f.link
+ if AL:
+ for l in AL:
+ l0, lo, hi = links[l]
+ if nlo<lo or nhi>hi:
+ links[l] = l0,min(nlo,lo),max(nhi,hi)
+
bg = getattr(f,'backColor',None)
if bg and not xs.backColor:
xs.backColor = bg
@@ -332,15 +347,19 @@
txtlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize)
cur_x += txtlen
nSpaces += text.count(' ')+_nbspCount(text)
+
cur_x_s = cur_x+(nSpaces-1)*ws
if last and pKind!='right' and xs.style.endDots:
_do_dots_frag(cur_x,cur_x_s,line.maxWidth,xs,tx)
- if xs.underline:
- xs.underlines.append( (xs.underline_x, cur_x_s, xs.underlineColor) )
- if xs.strike:
- xs.strikes.append( (xs.strike_x, cur_x_s, xs.strikeColor) )
- if xs.link:
- xs.links.append( (xs.link_x, cur_x_s, xs.link,xs.linkColor) )
+
+ if LL:
+ for l in LL:
+ us_lines[l] = us_lines[l], cur_x_s
+
+ if AL:
+ for l in AL:
+ links[l] = links[l], cur_x_s
+
if xs.backColor:
xs.backColors.append( (xs.backColor_x, cur_x_s, xs.backColor) )
if tx._x0!=x0:
@@ -708,7 +727,7 @@
bulletAnchor = style.bulletAnchor
if rtl or style.bulletAnchor!='start':
numeric = bulletAnchor=='numeric'
- if isinstance(bulletText,strTypes):
+ if isStr(bulletText):
t = bulletText
q = numeric and decimalSymbol in t
if q: t = t[:t.index(decimalSymbol)]
@@ -738,7 +757,7 @@
tx2 = canvas.beginText(bulletStart, cur_y)
tx2.setFont(style.bulletFontName, style.bulletFontSize)
tx2.setFillColor(getattr(style,'bulletColor',style.textColor))
- if isinstance(bulletText,strTypes):
+ if isStr(bulletText):
tx2.textOut(bulletText)
else:
for f in bulletText:
@@ -758,7 +777,7 @@
'''work out bullet width and adjust maxWidths[0] if neccessary
'''
if bulletText:
- if isinstance(bulletText,strTypes):
+ if isStr(bulletText):
bulletWidth = stringWidth( bulletText, style.bulletFontName, style.bulletFontSize)
else:
#it's a list of fragments
@@ -832,23 +851,38 @@
if j==lim:
i += 1
-def _old_do_line(tx, x1, y1, x2, y2):
- tx._canvas.line(x1, y1, x2, y2)
-
-def _do_line(tx, x1, y1, x2, y2):
- olw = tx._canvas._lineWidth
- nlw = tx._underlineProportion*tx._fontsize
+def _do_line(tx, x1, y1, x2, y2, nlw, nsc):
+ canv = tx._canvas
+ olw = canv._lineWidth
if nlw!=olw:
- tx._canvas.setLineWidth(nlw)
- tx._canvas.line(x1, y1, x2, y2)
- tx._canvas.setLineWidth(olw)
- else:
- tx._canvas.line(x1, y1, x2, y2)
+ canv.setLineWidth(nlw)
+ osc = canv._strokeColorObj
+ if nsc!=osc:
+ canv.setStrokeColor(nsc)
+ canv.line(x1, y1, x2, y2)
-def _do_under_line(i, t_off, ws, tx, lm=-0.125):
- y = tx.XtraState.cur_y - i*tx.XtraState.style.leading + lm*tx.XtraState.f.fontSize
- textlen = tx._canvas.stringWidth(' '.join(tx.XtraState.lines[i][1]), tx._fontname, tx._fontsize)
- tx._do_line(t_off, y, t_off+textlen, y)
+def _do_under_line(i, x1, ws, tx, us_lines):
+ xs = tx.XtraState
+ style = xs.style
+ y0 = xs.cur_y - i*style.leading
+ f = xs.f
+ fs = f.fontSize
+ tc = f.textColor
+ values = dict(L=fs,F=fs,f=fs)
+ dw = tx._defaultLineWidth
+ x2 = x1 + tx._canvas.stringWidth(' '.join(tx.XtraState.lines[i][1]), tx._fontname, fs)
+ for n,k,c,w,o,r,m,g in us_lines:
+ underline = k=='underline'
+ lw = _usConv(w,values,default=tx._defaultLineWidth)
+ lg = _usConv(g,values,default=1)
+ dy = lg+lw
+ if not underline: dy = -dy
+ y = y0 + r + _usConv(('-0.125*L' if underline else '0.25*L') if o=='' else o,values)
+ if not c: c = tc
+ while m>0:
+ tx._do_line(x1, y, x2, y, lw, c)
+ y -= dy
+ m -= 1
_scheme_re = re.compile('^[a-zA-Z][-+a-zA-Z0-9]+$')
def _doLink(tx,link,rect):
@@ -859,6 +893,7 @@
if kind=='GoToR': link = parts[1]
tx._canvas.linkURL(link, rect, relative=1, kind=kind)
else:
+ if not link: return
if link[0]=='#':
link = link[1:]
scheme=''
@@ -870,59 +905,55 @@
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)
- _doLink(tx, xs.link, (t_off, y, t_off+textlen, y+leading))
+ for n, link in xs.link:
+ _doLink(tx, link, (t_off, y, t_off+textlen, y+leading))
def _do_post_text(tx):
xs = tx.XtraState
- leading = xs.style.leading
- autoLeading = xs.autoLeading
y0 = xs.cur_y
f = xs.f
- ff = 0.125*f.fontSize
- yl = y0 + f.fontSize
+ leading = xs.style.leading
+ autoLeading = xs.autoLeading
+ fontSize = f.fontSize
if autoLeading=='max':
- leading = max(leading,1.2*f.fontSize)
+ leading = max(leading,1.2*fontSize)
elif autoLeading=='min':
- leading = 1.2*f.fontSize
- ydesc = yl - leading
+ leading = 1.2*fontSize
+
+ if xs.backColors:
+ yl = y0 + fontSize
+ ydesc = yl - leading
- for x1,x2,c in xs.backColors:
- tx._canvas.setFillColor(c)
- tx._canvas.rect(x1,ydesc,x2-x1,leading,stroke=0,fill=1)
- xs.backColors=[]
- xs.backColor=None
+ for x1,x2,c in xs.backColors:
+ tx._canvas.setFillColor(c)
+ tx._canvas.rect(x1,ydesc,x2-x1,leading,stroke=0,fill=1)
+ xs.backColors=[]
+ xs.backColor=None
- y = y0 - ff
- csc = None
- for x1,x2,c in xs.underlines:
- if c!=csc:
- tx._canvas.setStrokeColor(c)
- csc = c
- tx._do_line(x1, y, x2, y)
- xs.underlines = []
- xs.underline=0
- xs.underlineColor=None
+ for (((n,link),x1),lo,hi),x2 in sorted(xs.links.values()):
+ _doLink(tx, link, (x1, y0+lo, x2, y0+hi))
+ xs.links = {}
- ys = y0 + 2*ff
- for x1,x2,c in xs.strikes:
- if c!=csc:
- tx._canvas.setStrokeColor(c)
- csc = c
- tx._do_line(x1, ys, x2, ys)
- xs.strikes = []
- xs.strike=0
- xs.strikeColor=None
-
- for x1,x2,link,c in xs.links:
- if platypus_link_underline:
- if c!=csc:
- tx._canvas.setStrokeColor(c)
- csc = c
- tx._do_line(x1, y, x2, y)
- _doLink(tx, link, (x1, ydesc, x2, yl))
- xs.links = []
- xs.link=None
- xs.linkColor=None
+ if xs.us_lines:
+ #print 'lines'
+ dw = tx._defaultLineWidth
+ values = dict(L=fontSize)
+ for (((n,k,c,w,o,r,m,g),fs,tc,x1),fsmax),x2 in sorted(xs.us_lines.values()):
+ underline = k=='underline'
+ values['f'] = fs
+ values['F'] = fsmax
+ lw = _usConv(w,values,default=tx._defaultLineWidth)
+ lg = _usConv(g,values,default=1)
+ dy = lg+lw
+ if not underline: dy = -dy
+ y = y0 + r + _usConv(o if o!='' else ('-0.125*L' if underline else '0.25*L'),values)
+ #print 'n=%s k=%s x1=%s x2=%s r=%s c=%s w=%r o=%r fs=%r tc=%s y=%s lW=%r offs=%r' % (n,k,x1,x2,r,(c.hexval() if c else ''),w,o,fs,tc.hexval(),y,lW,y-y0-r)
+ if not c: c = tc
+ while m>0:
+ tx._do_line(x1, y, x2, y, lw, c)
+ y -= dy
+ m -= 1
+ xs.us_lines = {}
xs.cur_y -= leading
@@ -1102,6 +1133,10 @@
The paragraph Text can contain XML-like markup including the tags:
<b> ... </b> - bold
+ < u [color="red"] [width="pts"] [offset="pts"]> < /u > - underline
+ width and offset can be empty meaning use existing canvas line width
+ or with an f/F suffix regarded as a fraction of the font size
+ < strike > < /strike > - strike through has the same parameters as underline
<i> ... </i> - italics
<u> ... </u> - underline
<strike> ... </strike> - strike through
@@ -1112,19 +1147,21 @@
<onDraw name=callable label="a label"/>
<index [name="callablecanvasattribute"] label="a label"/>
<link>link text</link>
- attributes of links
- size/fontSize=num
- name/face/fontName=name
- fg/textColor/color=color
- backcolor/backColor/bgcolor=color
- dest/destination/target/href/link=target
+ attributes of links
+ size/fontSize/uwidth/uoffset=num
+ name/face/fontName=name
+ fg/textColor/color/ucolor=color
+ backcolor/backColor/bgcolor=color
+ dest/destination/target/href/link=target
+ underline=bool turn on underline
<a>anchor text</a>
- attributes of anchors
- fontSize=num
- fontName=name
- fg/textColor/color=color
- backcolor/backColor/bgcolor=color
- href=href
+ attributes of anchors
+ size/fontSize/uwidth/uoffset=num
+ fontName=name
+ fg/textColor/color/ucolor=color
+ backcolor/backColor/bgcolor=color
+ href=href
+ underline="yes|no"
<a name="anchorpoint"/>
<unichar name="unicode character name"/>
<unichar value="unicode code point"/>
@@ -1744,12 +1781,6 @@
canvas.setFillColor(f.textColor)
tx = self.beginText(cur_x, cur_y)
- if style.underlineProportion:
- tx._underlineProportion = style.underlineProportion
- tx._do_line = _do_line
- else:
- tx._do_line = _old_do_line
- tx._do_line = MethodType(tx._do_line,tx)
if autoLeading=='max':
leading = max(leading,blPara.ascent-blPara.descent)
elif autoLeading=='min':
@@ -1766,29 +1797,22 @@
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()
+ if f.us_lines or f.link or style.endDots:
+ tx._do_line = MethodType(_do_line,tx)
+ tx.xs = xs = tx.XtraState = ABag()
+ tx._defaultLineWidth = canvas._lineWidth
xs.cur_y = cur_y
xs.f = f
xs.style = style
xs.lines = lines
- xs.underlines=[]
- xs.underlineColor=None
- xs.strikes=[]
- xs.strikeColor=None
- xs.links=[]
xs.link=f.link
xs.textColor = f.textColor
xs.backColors = []
- canvas.setStrokeColor(f.textColor)
dx = t_off+leftIndent
if dpl!=_justifyDrawParaLine: ws = 0
- underline = f.underline or (f.link and platypus_link_underline)
- strike = f.strike
- link = f.link
- 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 f.us_lines:
+ _do_under_line(0, t_off, ws, tx, f.us_lines)
+ if f.link: _do_link_line(0, dx, ws, tx)
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.
@@ -1801,9 +1825,9 @@
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 f.us_lines:
+ _do_under_line(i, t_off, ws, tx, f.us_lines)
+ if f.link: _do_link_line(i, dx, ws, tx)
if lastLine and style.endDots and dpl!=_rightDrawParaLine: _do_dots(i, dx, ws, xs, tx, dpl)
else:
for i in xrange(1, nLines):
@@ -1839,12 +1863,12 @@
#set up the font etc.
tx = self.beginText(cur_x, cur_y)
- if style.underlineProportion:
- tx._underlineProportion = style.underlineProportion
- tx._do_line = _do_line
- else:
- tx._do_line = _old_do_line
- tx._do_line = MethodType(tx._do_line,tx)
+ tx._defaultLineWidth = canvas._lineWidth
+ tx._underlineWidth = getattr(style,'underlineWidth','')
+ tx._underlineOffset = getattr(style,'underlineOffset','') or '-0.125f'
+ tx._strikeWidth = getattr(style,'strikeWidth','')
+ tx._strikeOffset = getattr(style,'strikeOffset','') or '0.25f'
+ tx._do_line = MethodType(_do_line,tx)
# set the paragraph direction
tx.direction = self.style.wordWrap
@@ -1852,15 +1876,10 @@
xs.textColor=None
xs.backColor=None
xs.rise=0
- xs.underline=0
- xs.underlines=[]
- xs.underlineColor=None
- xs.strike=0
- xs.strikes=[]
- xs.strikeColor=None
xs.backColors=[]
- xs.links=[]
- xs.link=None
+ xs.us_lines = {}
+ xs.links = {}
+ xs.link={}
xs.leading = style.leading
xs.leftIndent = leftIndent
tx._leading = None
--- a/src/reportlab/platypus/paraparser.py Thu Mar 22 10:18:32 2018 +0000
+++ b/src/reportlab/platypus/paraparser.py Fri Mar 23 16:02:08 2018 +0000
@@ -20,6 +20,7 @@
from reportlab.lib.fonts import tt2ps, ps2tt
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
from reportlab.lib.units import inch,mm,cm,pica
+from reportlab.rl_config import platypus_link_underline
if isPy3:
from html.parser import HTMLParser
from html.entities import name2codepoint
@@ -47,29 +48,18 @@
except ValueError:
return float(s)*unit
-def _num(s, unit=1, allowRelative=True):
+def _num(s, unit=1, allowRelative=True,
+ _unit_map = {'i':inch,'in':inch,'pt':1,'cm':cm,'mm':mm,'pica':pica },
+ _re_unit = re.compile('^\s*(.*)(i|in|cm|mm|pt|pica)\s*$'),
+ ):
"""Convert a string like '10cm' to an int or float (in points).
The default unit is point, but optionally you can use other
default units like mm.
"""
- if s.endswith('cm'):
- unit=cm
- s = s[:-2]
- if s.endswith('in'):
- unit=inch
- s = s[:-2]
- if s.endswith('pt'):
- unit=1
- s = s[:-2]
- if s.endswith('i'):
- unit=inch
- s = s[:-1]
- if s.endswith('mm'):
- unit=mm
- s = s[:-2]
- if s.endswith('pica'):
- unit=pica
- s = s[:-4]
+ m = _re_unit.match(s)
+ if m:
+ unit = _unit_map[m.group(2)]
+ s = m.group(1)
return _convnum(s,unit,allowRelative)
def _int(s):
@@ -112,21 +102,53 @@
v = _numpct(getattr(frag,attr),allowRelative=True)
return (v[1]+frag.fontSize) if isinstance(v,tuple) else v.normalizedValue(frag.fontSize) if isinstance(v,_PCT) else v
-class _CheckSup:
- '''class for syntax checking <sup> attributes
- if the check succeeds then we always return the string for later evaluation
+class _ExValidate:
+ '''class for syntax checking attributes
'''
- def __init__(self,kind):
- self.kind = kind
- self.fontSize = 10
+ def __init__(self,tag,attr):
+ self.tag = tag
+ self.attr = attr
+
+ def invalid(self,s):
+ raise ValueError('<%s> invalid value %r for attribute %s' % (self.tag,s,self.attr))
+
+ def validate(self, parser,s):
+ raise ValueError('abstract method called')
+ return s
+
+ def __call__(self, parser, s):
+ try:
+ return self.validate(parser, s)
+ except:
+ self.invalid(s)
- def __call__(self,s):
- setattr(self,self.kind,s)
- try:
- fontSizeNormalize(self,self.kind,None)
- return s
- except:
- raise ValueError('<sup> invalid value %r for attribute %s' % (s,self.kind))
+class _CheckSup(_ExValidate):
+ '''class for syntax checking <sup|sub> attributes
+ if the check succeeds then we always return the string for later evaluation'''
+ def validate(self,parser,s):
+ self.fontSize = parser._stack[-1].fontSize
+ fontSizeNormalize(self,self.attr,'')
+ return s
+
+ def __call__(self, parser, s):
+ setattr(self,self.attr,s)
+ return _ExValidate.__call__(self,parser,s)
+
+_lineRepeats = dict(single=1,double=2,triple=3)
+_re_us_value = re.compile(r'^\s*(.*)\s*\*\s*(P|L|f|F)\s*$')
+class _CheckUS(_ExValidate):
+ '''class for syntax checking <u|strike> width/offset attributes'''
+ def validate(self,parser,s):
+ s = s.strip()
+ if s:
+ m = _re_us_value.match(s)
+ if m:
+ v = float(m.group(1))
+ if m.group(2)=='P':
+ return parser._stack[0].fontSize*v
+ else:
+ _num(s,allowRelative=False)
+ return s
def _valignpc(s):
s = s.lower()
@@ -211,7 +233,14 @@
'borderradius': ('borderRadius',_num),
'texttransform':('textTransform',_textTransformConv),
'enddots':('endDots',None),
- 'underlineproportion':('underlineProportion',_num),
+ 'underlinewidth':('underlineWidth',_CheckUS('para','underlineWidth')),
+ 'underlinecolor':('underlineColor',toColor),
+ 'underlineoffset':('underlineOffset',_CheckUS('para','underlineOffset')),
+ 'underlinegap':('underlineGap',_CheckUS('para','underlineGap')),
+ 'strikewidth':('strikeWidth',_CheckUS('para','strikeWidth')),
+ 'strikecolor':('strikeColor',toColor),
+ 'strikeoffset':('strikeOffset',_CheckUS('para','strikeOffset')),
+ 'strikegap':('strikeGap',_CheckUS('para','strikeGap')),
'spaceshrinkage':('spaceShrinkage',_num),
}
@@ -258,15 +287,15 @@
'destination': ('link', None),
'target': ('link', None),
'href': ('link', None),
+ 'ucolor': ('underlineColor', toColor),
+ 'uoffset': ('underlineOffset', _CheckUS('link','underlineOffset')),
+ 'uwidth': ('underlineWidth', _CheckUS('link','underlineWidth')),
+ 'ugap': ('underlineGap', _CheckUS('link','underlineGap')),
+ 'underline': ('underline',_bool),
+ 'ukind': ('underlineKind',None),
}
-_anchorAttrMap = {'fontSize': ('fontSize', _num),
- 'fontName': ('fontName', None),
+_anchorAttrMap = {
'name': ('name', None),
- 'fg': ('textColor', toColor),
- 'color':('textColor', toColor),
- 'backcolor':('backColor',toColor),
- 'bgcolor':('backColor',toColor),
- 'href': ('href', None),
}
_imgAttrMap = {
'src': ('src', None),
@@ -281,9 +310,23 @@
'format': ('format',None),
}
_supAttrMap = {
- 'rise': ('supr', _CheckSup('rise')),
- 'size': ('sups', _CheckSup('size')),
+ 'rise': ('supr', _CheckSup('sup|sub','rise')),
+ 'size': ('sups', _CheckSup('sup|sub','size')),
}
+_uAttrMap = {
+ 'color':('underlineColor', toColor),
+ 'width':('underlineWidth', _CheckUS('underline','underlineWidth')),
+ 'offset':('underlineOffset', _CheckUS('underline','underlineOffset')),
+ 'gap':('underlineGap', _CheckUS('underline','underlineGap')),
+ 'kind':('underlineKind',None),
+ }
+_strikeAttrMap = {
+ 'color':('strikeColor', toColor),
+ 'width':('strikeWidth', _CheckUS('strike','strikeWidth')),
+ 'offset':('strikeOffset', _CheckUS('strike','strikeOffset')),
+ 'gap':('strikeGap', _CheckUS('strike','strikeGap')),
+ 'kind':('strikeKind',None),
+ }
def _addAttributeNames(m):
K = list(m.keys())
@@ -613,8 +656,10 @@
# tags:
# < /b > - bold
# < /i > - italics
-# < u > < /u > - underline
-# < strike > < /strike > - strike through
+# < u [color="red"] [width="pts"] [offset="pts"]> < /u > - underline
+# width and offset can be empty meaning use existing canvas line width
+# or with an f/F suffix regarded as a fraction of the font size
+# < strike > < /strike > - strike through has the same parameters as underline
# < super [size="pts"] [rise="pts"]> < /super > - superscript
# < sup ="pts"] [rise="pts"]> < /sup > - superscript
# < sub ="pts"] [rise="pts"]> < /sub > - subscript
@@ -624,14 +669,15 @@
# <onDraw name=callable label="a label"/>
# <index [name="callablecanvasattribute"] label="a label"/>
# <link>link text</link>
-# attributes of links
-# size/fontSize=num
+# attributes of links
+# size/fontSize/uwidth/uoffset=num
# name/face/fontName=name
-# fg/textColor/color=color
+# fg/textColor/color/ucolor=color
# backcolor/backColor/bgcolor=color
# dest/destination/target/href/link=target
+# underline=bool turn on underline
# <a>anchor text</a>
-# attributes of anchors
+# attributes of anchors
# fontSize=num
# fontName=name
# fg/textColor/color=color
@@ -698,23 +744,53 @@
def end_em( self ):
self._pop('em')
+ def _new_line(self,k):
+ frag = self._stack[-1]
+ frag.us_lines = frag.us_lines + [(
+ self.nlines,
+ k,
+ getattr(frag,k+'Color',None),
+ getattr(frag,k+'Width',self._defaultLineWidths[k]),
+ getattr(frag,k+'Offset',self._defaultLineOffsets[k]),
+ frag.rise,
+ _lineRepeats[getattr(frag,k+'Kind','single')],
+ getattr(frag,k+'Gap',self._defaultLineGaps[k]),
+ )]
+ self.nlines += 1
+
#### underline
def start_u( self, attributes ):
- self._push('u',underline=1)
+ A = self.getAttributes(attributes,_uAttrMap)
+ self._push('u',**A)
+ self._new_line('underline')
def end_u( self ):
self._pop('u')
#### strike
def start_strike( self, attributes ):
- self._push('strike',strike=1)
+ A = self.getAttributes(attributes,_strikeAttrMap)
+ self._push('strike',strike=1,**A)
+ self._new_line('strike')
def end_strike( self ):
self._pop('strike')
#### link
- def start_link(self, attributes):
- self._push('link',**self.getAttributes(attributes,_linkAttrMap))
+ def _handle_link(self, tag, attributes):
+ A = self.getAttributes(attributes,_linkAttrMap)
+ underline = A.pop('underline',self._defaultLinkUnderline)
+ A['link'] = self._stack[-1].link + [(
+ self.nlinks,
+ A.pop('link','').strip(),
+ )]
+ self.nlinks += 1
+ self._push(tag,**A)
+ if underline:
+ self._new_line('underline')
+
+ def start_link(self,attributes):
+ self._handle_link('link',attributes)
def end_link(self):
if self._pop('link').link is None:
@@ -722,9 +798,10 @@
#### anchor
def start_a(self, attributes):
- A = self.getAttributes(attributes,_anchorAttrMap)
- name = A.get('name',None)
- if name is not None:
+ anchor = 'name' in attributes
+ if anchor:
+ A = self.getAttributes(attributes,_anchorAttrMap)
+ name = A.get('name',None)
name = name.strip()
if not name:
self._syntax_error('<a name="..."/> anchor variant requires non-blank name')
@@ -732,11 +809,9 @@
self._syntax_error('<a name="..."/> anchor variant only allows name attribute')
A = dict(name=A['name'])
A['_selfClosingTag'] = 'anchor'
+ self._push('a',**A)
else:
- href = A.get('href','').strip()
- A['link'] = href #convert to our link form
- A.pop('href',None)
- self._push('a',**A)
+ self._handle_link('a',attributes)
def end_a(self):
frag = self._stack[-1]
@@ -780,25 +855,25 @@
#### super script
def start_super( self, attributes ):
A = self.getAttributes(attributes,_supAttrMap)
- A['sup']=1
+ #A['sup']=1
self._push('super',**A)
+ frag = self._stack[-1]
+ frag.rise += fontSizeNormalize(frag,'supr',frag.fontSize*supFraction)
+ frag.fontSize = fontSizeNormalize(frag,'sups',frag.fontSize-min(sizeDelta,0.2*frag.fontSize))
def end_super( self ):
self._pop('super')
- def start_sup( self, attributes ):
- A = self.getAttributes(attributes,_supAttrMap)
- A['sup']=1
- self._push('sup',**A)
-
- def end_sup( self ):
- self._pop('sup')
+ start_sup = start_super
+ end_sup = end_super
#### sub script
def start_sub( self, attributes ):
A = self.getAttributes(attributes,_supAttrMap)
- A['sub']=1
self._push('sub',**A)
+ frag = self._stack[-1]
+ frag.rise -= fontSizeNormalize(frag,'supr',frag.fontSize*subFraction)
+ frag.fontSize = fontSizeNormalize(frag,'sups',frag.fontSize-min(sizeDelta,0.2*frag.fontSize))
def end_sub( self ):
self._pop('sub')
@@ -888,7 +963,7 @@
def start_br(self, attr):
self._push('br',_selfClosingTag='br',lineBreak=True,text='')
-
+
def end_br(self):
#print('\nend_br called, %d frags in list' % len(self.fragList))
frag = self._stack[-1]
@@ -907,13 +982,9 @@
# initialize semantic values
frag = ParaFrag()
- frag.sub = 0
- frag.sup = 0
frag.rise = 0
- frag.underline = 0
- frag.strike = 0
frag.greek = 0
- frag.link = None
+ frag.link = []
if bullet:
frag.fontName, frag.bold, frag.italic = ps2tt(style.bulletFontName)
frag.fontSize = style.bulletFontSize
@@ -922,6 +993,21 @@
frag.fontName, frag.bold, frag.italic = ps2tt(style.fontName)
frag.fontSize = style.fontSize
frag.textColor = style.textColor
+ frag.us_lines = []
+ self.nlinks = self.nlines = 0
+ self._defaultLineWidths = dict(
+ underline = getattr(style,'underlineWidth',''),
+ strike = getattr(style,'strikeWidth',''),
+ )
+ self._defaultLineOffsets = dict(
+ underline = getattr(style,'underlineOffset',''),
+ strike = getattr(style,'strikeOffset',''),
+ )
+ self._defaultLineGaps = dict(
+ underline = getattr(style,'underlineGap',''),
+ strike = getattr(style,'strikeGap',''),
+ )
+ self._defaultLinkUnderline = getattr(style,'linkUnderline',platypus_link_underline)
return frag
def start_para(self,attr):
@@ -1041,7 +1127,7 @@
self._push('ondraw',cbDefn=defn)
self.handle_data('')
self._pop('ondraw')
- start_onDraw=start_ondraw
+ start_onDraw=start_ondraw
end_onDraw=end_ondraw=end_seq
def start_index(self,attr):
@@ -1096,7 +1182,10 @@
if k in attrMap:
j = attrMap[k]
func = j[1]
- A[j[0]] = v if func is None else func(v)
+ if func is not None:
+ #it's a function
+ v = func(self,v) if isinstance(func,_ExValidate) else func(v)
+ A[j[0]] = v
else:
self._syntax_error('invalid attribute name %s attrMap=%r'% (k,list(sorted(attrMap.keys()))))
return A
@@ -1144,23 +1233,12 @@
if data!='': self._syntax_error('No content allowed in %s tag' % frag._selfClosingTag)
return
else:
- # if sub and sup are both on they will cancel each other out
- if frag.sub == 1 and frag.sup == 1:
- frag.sub = 0
- frag.sup = 0
-
- if frag.sub:
- frag.rise = -fontSizeNormalize(frag,'supr',frag.fontSize*subFraction)
- frag.fontSize = fontSizeNormalize(frag,'sups',frag.fontSize-min(sizeDelta,0.2*frag.fontSize))
- elif frag.sup:
- frag.rise = fontSizeNormalize(frag,'supr',frag.fontSize*supFraction)
- frag.fontSize = fontSizeNormalize(frag,'sups',frag.fontSize-min(sizeDelta,0.2*frag.fontSize))
-
+ #get the right parameters for the
if frag.greek:
frag.fontName = 'symbol'
data = _greekConvert(data)
- # bold, italic, and underline
+ # bold, italic
frag.fontName = tt2ps(frag.fontName,frag.bold,frag.italic)
#save our data
@@ -1181,6 +1259,8 @@
def _complete_parse(self):
"Reset after parsing, to be ready for next paragraph"
+ if self._stack:
+ self._syntax_error('parse ended with %d unclosed tags\n %s' % (len(self._stack),'\n '.join((x.__tag__ for x in reversed(self._stack)))))
del self._seq
style = self._style
del self._style
@@ -1267,7 +1347,7 @@
start = self.start_unknown
#call it
start(attrs or {})
-
+
def handle_endtag(self, tag):
"Called by HTMLParser when a tag ends"
#find the existing end_tagname method
--- a/src/reportlab/rl_settings.py Thu Mar 22 10:18:32 2018 +0000
+++ b/src/reportlab/rl_settings.py Fri Mar 23 16:02:08 2018 +0000
@@ -48,12 +48,17 @@
T1SearchPath
TTFSearchPath
CMapSearchPath
-baseUnderlineProportion
decimalSymbol
errorOnDuplicatePageLabelPage
autoGenerateMissingTTFName
allowTTFSubsetting
-spaceShrinkage'''.split())
+spaceShrinkage
+underlineWidth
+underlineOffset
+underlineGap
+strikeWidth
+strikeOffset
+strikeGap'''.split())
allowTableBoundsErrors = 1 # set to 0 to die on too large elements in tables in debug (recommend 1 for production use)
shapeChecking = 1
@@ -103,7 +108,21 @@
rtlSupport= 0 #set to 1 to attempt import of RTL assistance eg fribidi etc etc
listWrapOnFakeWidth= 1 #set to 0/False to force platypus.flowables._listWrapOn to report correct widths
#else it reports minimum(required,available) width
-baseUnderlineProportion= 0.0 #non-zero for doing font size proportional lines in Paragraph.py
+
+underlineWidth= '' #empty to use canvas strokeWidth or a distance or number*<letter>
+ # num * <letter> make value proportional to a font size
+ # P paragraph font size
+ # L line max font size
+ # f first use font size
+ # F max fontsize in the tag
+
+underlineOffset= '-0.125*F' #fraction of fontSize from baseline to draw underlines at.
+underlineGap= '1' #gap for double/triple underline
+
+strikeWidth= ''
+strikeOffset= '0.25*F' #fraction of fontSize from baseline to draw strike through at.
+strikeGap= '1' #gap for double/triple strike
+
#by default typical value 0.05. may be overridden on a parastyle.
decimalSymbol= '.' #what we use to align floats numerically
errorOnDuplicatePageLabelPage= 0 #if True will cause repeated PageLabel page numbers to raise an error.
--- a/src/rl_addons/rl_accel/_rl_accel.c Thu Mar 22 10:18:32 2018 +0000
+++ b/src/rl_addons/rl_accel/_rl_accel.c Fri Mar 23 16:02:08 2018 +0000
@@ -29,7 +29,7 @@
#ifndef min
# define min(a,b) ((a)<(b)?(a):(b))
#endif
-#define VERSION "0.72"
+#define VERSION "0.73"
#define MODULE "_rl_accel"
struct module_state {
@@ -444,7 +444,7 @@
static PyObject *sameFrag(PyObject *module, PyObject* args)
{
PyObject *f, *g;
- static char *names[] = {"fontName", "fontSize", "textColor", "rise", "underline", "strike", "link", "backColor", NULL};
+ static char *names[] = {"fontName", "fontSize", "textColor", "rise", "us_lines", "link", "backColor", NULL};
int r=0, t;
char **p;
if (!PyArg_ParseTuple(args, "OO:sameFrag", &f, &g)) return NULL;
--- a/tests/test_paragraphs.py Thu Mar 22 10:18:32 2018 +0000
+++ b/tests/test_paragraphs.py Fri Mar 23 16:02:08 2018 +0000
@@ -253,7 +253,7 @@
SA(Paragraph('A very much' +50*' longer'+' justified complex <font color="green">paragraph</font> with equals', i and styDotsJCJK or styDotsJ))
SA(Paragraph('A very much' +50*' longer'+' justified complex <font color="green">indented paragraph</font> with equals', i and istyDotsJCJK or istyDotsJ))
- SA(Paragraph('<para>This image has the image data embedded in the src attribute: <img src="DATA:image/gif;base64,R0lGODdhDwAMAIACAAAAAPf/EiwAAAAADwAMAAACHIyPqcvtCl4ENEQzbc3XnjtV2Dd247mBnva0SwEAOw==" />', styNormal))
+ SA(Paragraph('<para>This image has the image data embedded in the src attribute: <img src="DATA:image/gif;base64,R0lGODdhDwAMAIACAAAAAPf/EiwAAAAADwAMAAACHIyPqcvtCl4ENEQzbc3XnjtV2Dd247mBnva0SwEAOw==" /></para>', styNormal))
template = SimpleDocTemplate(outputfile('test_paragraphs.pdf'),
showBoundary=1)
template.build(story,
--- a/tests/test_platypus_paragraphs.py Thu Mar 22 10:18:32 2018 +0000
+++ b/tests/test_platypus_paragraphs.py Fri Mar 23 16:02:08 2018 +0000
@@ -120,7 +120,7 @@
sty.wordWrap = 'CJK'
p0=Paragraph('ABCDEFGHIJKL]N',sty)
p1=Paragraph('AB<font color="red">C</font>DEFGHIJKL]N',sty)
- canv = Canvas('test_platypus_paragraph_cjk3.pdf')
+ canv = Canvas(outputfile('test_platypus_paragraph_cjk3.pdf'))
ix = len(canv._code)
aW = pdfmetrics.stringWidth('ABCD','Courier',15)
w,h=p0.wrap(aW,1000000)
@@ -199,14 +199,17 @@
should be a pound sign &pound;="£" and this an alpha &alpha;="α". You can have links in the page <link href="http://www.reportlab.com" color="blue">ReportLab</link> & <a href="http://www.reportlab.org" color="green">ReportLab.org</a>.
Use scheme "pdf:" to indicate an external PDF link, "http:", "https:" to indicate an external link eg something to open in
your browser. If an internal link begins with something that looks like a scheme, precede with "document:". Empty hrefs should be allowed ie <a href=""><a href="">test</a></a> should be allowed.
-<u>This text should be underlined.</u>
-<strike>This text should have a strike through it.</strike>
-<span backcolor="yellow"><strike>This text should have a strike through it and be highlighted.</strike></span>
-<span backcolor="yellow"><strike><u>This text should have a strike through it and be highlighted and underlined.</u></strike></span>
-This should be a mailto link <a href="mailto:reportlab-users@lists2.reportlab.com"><font color="blue">reportlab-users at lists2.reportlab.com</font></a>.
-This should be an underlined mailto link <a href="mailto:reportlab-users@lists2.reportlab.com"><font color="blue">reportlab-users at lists2.reportlab.com</font></a>.
-This should be a highlighted mailto link <span backcolor="yellow"><a href="mailto:reportlab-users@lists2.reportlab.com"><font color="blue">reportlab-users at lists2.reportlab.com</font></a></span>.
-This should be a highlighted & undelined mailto link <span backcolor="yellow"><a href="mailto:reportlab-users@lists2.reportlab.com"><u><font color="blue">reportlab-users at lists2.reportlab.com</font></u></a></span>.
+<u>This text should be underlined.</u><br/>
+<strike>This text should have a strike through it.</strike><br/>
+<span backcolor="yellow"><strike>This text should have a strike through it and be highlighted.</strike></span><br/>
+<span backcolor="yellow"><strike><u>This text should have a strike through it and be highlighted and underlined.</u></strike></span><br/>
+This should be a mailto link <a href="mailto:reportlab-users@lists2.reportlab.com"><font color="blue">reportlab-users at lists2.reportlab.com</font></a>.<br/>
+This should be an underlined mailto link <a underline="1" href="mailto:reportlab-users@lists2.reportlab.com"><font color="blue">reportlab-users at lists2.reportlab.com</font></a>.<br/>
+This should be a highlighted mailto link <span backcolor="yellow"><a href="mailto:reportlab-users@lists2.reportlab.com"><font color="blue">reportlab-users at lists2.reportlab.com</font></a></span>.<br/>
+This should be a highlighted & underlined mailto link <span backcolor="yellow"><a underline="1" ucolor="red" uwidth="0.01*F" href="mailto:reportlab-users@lists2.reportlab.com"><font color="blue">reportlab-users at lists2.reportlab.com</font></a></span>.<br/>
+<u offset="-.125*F">Underlined <font size="-1">Underlined</font></u><br/>
+This is A<sup><u>underlined</u></sup> as is A<u><sup>this</sup></u>
+<u color="red">This is A<sup><u>underlined</u></sup> as is A<u><sup>this</sup></u></u>
'''
from reportlab.platypus.flowables import ImageAndFlowables, Image
from reportlab.lib.testutils import testsFolder
@@ -459,6 +462,30 @@
t,' '.join((n+1)*['A']),t,text0,t,' '.join((n+1)*['A']),t,text1),
style=s))
a(Paragraph("The jump at the beginning should come here <a name=\"theEnd\"/><a name=\"theEnd\"/>!",style=normal))
+ a(Paragraph('Underlining <span fontSize="11"><u color="red">A<u color="green">B</u><u color="blue">C</u>D<sup><strike width="0.5" color="magenta">2</strike><sup><u color="darkgreen" width="0.2">3</u></sup></sup></u></span>',normal))
+ a(Paragraph('<para autoLeading="max" spaceAfter="10">this is in 12 <font size=30>this is in 30</font> <u offset="-0.5" width="0.5" color="red"><u offset="-1.5" width="0.5" color="blue">and</u></u> <link underline="1" ucolor="blue" href="http://google.com/">the link box<sup><a color="red" ucolor="green" underline="1" href="https://www.reportlab.com">2</a></sup> is right (twice).</link></para>''',normal))
+ a(Paragraph('<para autoLeading="max" spaceAfter="10">this is in 12 <font size=30>this is in 30</font> and <link underline="1" ucolor="blue" href="http://google.com/">the link box is right.</link></para>''',normal))
+ a(Paragraph('Underlining <u><span color="red">underlined in red? <span color="blue"><u>or blue</u></span> or red again?</span></u>',normal))
+ a(Paragraph('Link <a href="#theEnd" color="blue">jump</a> to end.<br/>Underlined link <a href="#theEnd" underline="1" ucolor="red" color="blue">jump</a> to end!',style=normal))
+ a(Paragraph('<para autoleading=""><u>A</u>. Furthermore, a subset of <font size="14">English sentences</font> interesting on quite\nindependent grounds is not quite equivalent to a stipulation to place\nthe constructions into these various categories. <u>A</u>. We will bring evidence in favor of\nThe following thesis: most of the methodological work in modern\nlinguistics can be defined in such a way as to impose problems of\nphonemic and morphological analysis.</para>',normal))
+ a(Paragraph('<para autoleading=""><u>A</u>. Furthermore, a subset of <font size="14">English sentences</font> interesting on quite<br/><u>A</u>.</para>',normal))
+ a(Paragraph(u"<para>This is a <sup rise=5><span color='red'>sup</span></sup>rise=5.</para>",normal))
+ a(Paragraph('<span fontSize="11"><u color="green"><strike color="blue">AAAAAA</strike></u></span>',normal))
+ a(Paragraph("Underlining & width proportional to first use font size ('f' suffix) <u offset='-0.125*f' width='0.05*f'>underlined <span size=14>underlined</span></u>!",style=normal))
+ a(Paragraph("Underlining & width proportional to first use font size ('F' suffix) <u offset='-0.125*F' width='0.05*F'>underlined <span size=14>underlined</span></u>!",style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is underlined <sup>: a<sup><u><span color="red">sup</span></u></sup></para>''',style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is <u>underlined</u></para>''',style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is <u kind="double">underlined double</u></para>''',style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is <strike>striken</strike></para>''',style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is <strike><u>both</u></strike></para>''',style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is <u width="0.5" offset="-1" kind="double">underlined kind="double"</u></para>''',style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is <u width="0.25" offset="-1" kind="double">double underlined with thinner lines</u></para>''',style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is <u width="0.5" offset="-0.5" color="red">underlined in red</u></para>''',style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is <strike width="0.5" color="red">overstruck in red</strike></para>''',style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is <strike width="0.5" color="red" kind="double">doubly overstruck in red</strike></para>''',style=normal))
+ a(Paragraph('''<para spaceBefore="10">This is <strike width="0.5" offset="0.125*F" color="red" kind="triple" gap="0.5">triply overstruck in red</strike></para>''',style=normal))
+ a(Paragraph('''<para autoLeading="max" spaceAfter="10" spaceBefore="30">this is in 12 <font size="30">this is in 30</font> <u offset="-0.5" width="0.5" color="red"><u offset="-1.5" width="0.5" color="blue">and</u></u> <link underline="1" ucolor="blue" href="http://google.com/">the link box<sup><a color="red" ucolor="green" underline="1" href="https://www.reportlab.com">2</a></sup> is right (twice).</link></para>''',style=normal))
+ a(Paragraph("",style=normal))
doc = MyDocTemplate(outputfile('test_platypus_paragraphs_ul.pdf'))
doc.build(story)
--- a/tests/test_rl_accel.py Thu Mar 22 10:18:32 2018 +0000
+++ b/tests/test_rl_accel.py Fri Mar 23 16:02:08 2018 +0000
@@ -157,9 +157,9 @@
for func,kind in getFuncs('sameFrag'):
if not func: continue
- a=ABag(fontName='Helvetica',fontSize=12, textColor="red", rise=0, underline=0, strike=0, link="aaaa")
- b=ABag(fontName='Helvetica',fontSize=12, textColor="red", rise=0, underline=0, strike=0, link="aaaa")
- for name in ("fontName", "fontSize", "textColor", "rise", "underline", "strike", "link"):
+ a=ABag(fontName='Helvetica',fontSize=12, textColor="red", rise=0, us_lines=0, link="aaaa")
+ b=ABag(fontName='Helvetica',fontSize=12, textColor="red", rise=0, us_lines=0, link="aaaa")
+ for name in ("fontName", "fontSize", "textColor", "rise", "us_lines", "link"):
old = getattr(a,name)
assert func(a,b)==1, "%s sameFrag(%s,%s)!=1" % (kind,a,b)
assert func(b,a)==1, "%s sameFrag(%s,%s)!=1" % (kind,b,a)