"Fossies" - the Fresh Open Source Software Archive

Member "xhtml2pdf-0.2.5/xhtml2pdf/reportlab_paragraph.py" (8 Oct 2020, 70934 Bytes) of package /linux/www/xhtml2pdf-0.2.5.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "reportlab_paragraph.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.2.4_vs_0.2.5.

    1 # -*- coding: utf-8 -*-
    2 # Copyright ReportLab Europe Ltd. 2000-2008
    3 # see license.txt for license details
    4 # history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/paragraph.py
    5 # Modifications by Dirk Holtwick, 2008
    6 
    7 from __future__ import unicode_literals
    8 import re
    9 import six
   10 import sys
   11 
   12 
   13 basestring = six.text_type
   14 unicode = six.text_type #python 3
   15 str = six.text_type
   16 ###############################################################
   17 ###############################################################
   18 ###############################################################
   19 
   20 from string import whitespace
   21 from operator import truth
   22 from reportlab.pdfbase.pdfmetrics import stringWidth, getAscentDescent
   23 from reportlab.platypus.paraparser import ParaParser
   24 from reportlab.platypus.flowables import Flowable
   25 from reportlab.lib.colors import Color
   26 from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
   27 from reportlab.lib.textsplit import ALL_CANNOT_START
   28 from copy import deepcopy
   29 from reportlab.lib.abag import ABag
   30 from xhtml2pdf.util import getSize
   31 
   32 
   33 PARAGRAPH_DEBUG = False
   34 LEADING_FACTOR = 1.0
   35 
   36 _wsc_re_split = re.compile('[%s]+' % re.escape(''.join((
   37     u'\u0009',  # HORIZONTAL TABULATION
   38     u'\u000A',  # LINE FEED
   39     u'\u000B',  # VERTICAL TABULATION
   40     u'\u000C',  # FORM FEED
   41     u'\u000D',  # CARRIAGE RETURN
   42     u'\u001C',  # FILE SEPARATOR
   43     u'\u001D',  # GROUP SEPARATOR
   44     u'\u001E',  # RECORD SEPARATOR
   45     u'\u001F',  # UNIT SEPARATOR
   46     u'\u0020',  # SPACE
   47     u'\u0085',  # NEXT LINE
   48     #u'\u00A0', # NO-BREAK SPACE
   49     u'\u1680',  # OGHAM SPACE MARK
   50     u'\u2000',  # EN QUAD
   51     u'\u2001',  # EM QUAD
   52     u'\u2002',  # EN SPACE
   53     u'\u2003',  # EM SPACE
   54     u'\u2004',  # THREE-PER-EM SPACE
   55     u'\u2005',  # FOUR-PER-EM SPACE
   56     u'\u2006',  # SIX-PER-EM SPACE
   57     u'\u2007',  # FIGURE SPACE
   58     u'\u2008',  # PUNCTUATION SPACE
   59     u'\u2009',  # THIN SPACE
   60     u'\u200A',  # HAIR SPACE
   61     u'\u200B',  # ZERO WIDTH SPACE
   62     u'\u2028',  # LINE SEPARATOR
   63     u'\u2029',  # PARAGRAPH SEPARATOR
   64     u'\u202F',  # NARROW NO-BREAK SPACE
   65     u'\u205F',  # MEDIUM MATHEMATICAL SPACE
   66     u'\u3000',  # IDEOGRAPHIC SPACE
   67 )))).split
   68 
   69 
   70 def split(text, delim=None):
   71     if type(text) is bytes:
   72         text = text.decode('utf8')
   73     if type(delim) is bytes:
   74         delim = delim.decode('utf8')
   75     elif delim is None and u'\xa0' in text:
   76         return [uword.encode('utf8') for uword in _wsc_re_split(text)]
   77     return [uword.encode('utf8') for uword in text.split(delim)]
   78 
   79 
   80 def strip(text):
   81     if type(text) is bytes:
   82         text = text.decode('utf8')
   83     return text.strip().encode('utf8')
   84 
   85 
   86 class ParaLines(ABag):
   87     """
   88     class ParaLines contains the broken into lines representation of Paragraphs
   89         kind=0  Simple
   90         fontName, fontSize, textColor apply to whole Paragraph
   91         lines   [(extraSpace1,words1),....,(extraspaceN,wordsN)]
   92 
   93         kind==1 Complex
   94         lines   [FragLine1,...,FragLineN]
   95     """
   96 
   97 
   98 class FragLine(ABag):
   99     """
  100     class FragLine contains a styled line (ie a line with more than one style)::
  101 
  102     extraSpace  unused space for justification only
  103     wordCount   1+spaces in line for justification purposes
  104     words       [ParaFrags] style text lumps to be concatenated together
  105     fontSize    maximum fontSize seen on the line; not used at present,
  106                 but could be used for line spacing.
  107     """
  108 
  109 #our one and only parser
  110 # XXXXX if the parser has any internal state using only one is probably a BAD idea!
  111 _parser = ParaParser()
  112 
  113 
  114 def _lineClean(L):
  115     return b" ".join( filter(truth, split(strip(L))) )
  116 
  117 
  118 
  119 def cleanBlockQuotedText(text, joiner=b' '):
  120     """This is an internal utility which takes triple-
  121     quoted text form within the document and returns
  122     (hopefully) the paragraph the user intended originally."""
  123     L = filter(truth, map(_lineClean, split(text, '\n')))
  124     return joiner.join(L)
  125 
  126 def setXPos(tx, dx):
  127     if dx > 1e-6 or dx < -1e-6:
  128         tx.setXPos(dx)
  129 
  130 
  131 def _leftDrawParaLine(tx, offset, extraspace, words, last=0):
  132     setXPos(tx, offset)
  133     tx._textOut(b" ".join(words), 1)
  134     setXPos(tx, -offset)
  135     return offset
  136 
  137 
  138 def _centerDrawParaLine(tx, offset, extraspace, words, last=0):
  139     m = offset + 0.5 * extraspace
  140     setXPos(tx, m)
  141     tx._textOut(b" ".join(words), 1)
  142     setXPos(tx, -m)
  143     return m
  144 
  145 
  146 def _rightDrawParaLine(tx, offset, extraspace, words, last=0):
  147     m = offset + extraspace
  148     setXPos(tx, m)
  149     tx._textOut(b" ".join(words), 1)
  150     setXPos(tx, -m)
  151     return m
  152 
  153 
  154 def _justifyDrawParaLine(tx, offset, extraspace, words, last=0):
  155     setXPos(tx, offset)
  156     text = b" ".join(words)
  157     if last:
  158         #last one, left align
  159         tx._textOut(text, 1)
  160     else:
  161         nSpaces = len(words) - 1
  162         if nSpaces:
  163             tx.setWordSpace(extraspace / float(nSpaces))
  164             tx._textOut(text, 1)
  165             tx.setWordSpace(0)
  166         else:
  167             tx._textOut(text, 1)
  168     setXPos(tx, -offset)
  169     return offset
  170 
  171 
  172 def imgVRange(h, va, fontSize):
  173     """
  174     return bottom,top offsets relative to baseline(0)
  175     """
  176 
  177     if va == 'baseline':
  178         iyo = 0
  179     elif va in ('text-top', 'top'):
  180         iyo = fontSize - h
  181     elif va == 'middle':
  182         iyo = fontSize - (1.2 * fontSize + h) * 0.5
  183     elif va in ('text-bottom', 'bottom'):
  184         iyo = fontSize - 1.2 * fontSize
  185     elif va == 'super':
  186         iyo = 0.5 * fontSize
  187     elif va == 'sub':
  188         iyo = -0.5 * fontSize
  189     elif hasattr(va, 'normalizedValue'):
  190         iyo = va.normalizedValue(fontSize)
  191     else:
  192         iyo = va
  193     return iyo, iyo + h
  194 
  195 
  196 _56 = 5. / 6
  197 _16 = 1. / 6
  198 
  199 
  200 def _putFragLine(cur_x, tx, line):
  201     xs = tx.XtraState
  202     cur_y = xs.cur_y
  203     x0 = tx._x0
  204     autoLeading = xs.autoLeading
  205     leading = xs.leading
  206     cur_x += xs.leftIndent
  207     dal = autoLeading in ('min', 'max')
  208     if dal:
  209         if autoLeading == 'max':
  210             ascent = max(_56 * leading, line.ascent)
  211             descent = max(_16 * leading, -line.descent)
  212         else:
  213             ascent = line.ascent
  214             descent = -line.descent
  215         leading = ascent + descent
  216     if tx._leading != leading:
  217         tx.setLeading(leading)
  218     if dal:
  219         olb = tx._olb
  220         if olb is not None:
  221             xcy = olb - ascent
  222             if tx._oleading != leading:
  223                 cur_y += leading - tx._oleading
  224             if abs(xcy - cur_y) > 1e-8:
  225                 cur_y = xcy
  226                 tx.setTextOrigin(x0, cur_y)
  227                 xs.cur_y = cur_y
  228         tx._olb = cur_y - descent
  229         tx._oleading = leading
  230 
  231     # Letter spacing
  232     if xs.style.letterSpacing != 'normal':
  233         letter_spacing = getSize("".join(xs.style.letterSpacing), line.fontSize)
  234         tx.setCharSpace(letter_spacing)
  235 
  236     ws = getattr(tx, '_wordSpace', 0)
  237     nSpaces = 0
  238     words = line.words
  239     for f in words:
  240         if hasattr(f, 'cbDefn'):
  241             cbDefn = f.cbDefn
  242             kind = cbDefn.kind
  243             if kind == 'img':
  244                 #draw image cbDefn,cur_y,cur_x
  245                 w = cbDefn.width
  246                 h = cbDefn.height
  247                 txfs = tx._fontsize
  248                 if txfs is None:
  249                     txfs = xs.style.fontSize
  250                 iy0, iy1 = imgVRange(h, cbDefn.valign, txfs)
  251                 cur_x_s = cur_x + nSpaces * ws
  252                 tx._canvas.drawImage(cbDefn.image.getImage(), cur_x_s, cur_y + iy0, w, h, mask='auto')
  253                 cur_x += w
  254                 cur_x_s += w
  255                 setXPos(tx, cur_x_s - tx._x0)
  256             elif kind == 'barcode':
  257                 barcode = cbDefn.barcode
  258                 w = cbDefn.width
  259                 h = cbDefn.height
  260                 txfs = tx._fontsize
  261                 if txfs is None:
  262                     txfs = xs.style.fontSize
  263                 iy0, iy1 = imgVRange(h, cbDefn.valign, txfs)
  264                 cur_x_s = cur_x + nSpaces * ws
  265                 barcode.draw(canvas=tx._canvas, xoffset=cur_x_s)
  266                 cur_x += w
  267                 cur_x_s += w
  268                 setXPos(tx, cur_x_s - tx._x0)
  269             else:
  270                 name = cbDefn.name
  271                 if kind == 'anchor':
  272                     tx._canvas.bookmarkHorizontal(name, cur_x, cur_y + leading)
  273                 else:
  274                     func = getattr(tx._canvas, name, None)
  275                     if not func:
  276                         raise AttributeError("Missing %s callback attribute '%s'" % (kind, name))
  277                     func(tx._canvas, kind, cbDefn.label)
  278             if f is words[-1]:
  279                 if not tx._fontname:
  280                     tx.setFont(xs.style.fontName, xs.style.fontSize)
  281                     tx._textOut('', 1)
  282                 elif kind == 'img':
  283                     tx._textOut('', 1)
  284         else:
  285             cur_x_s = cur_x + nSpaces * ws
  286             if (tx._fontname, tx._fontsize) != (f.fontName, f.fontSize):
  287                 tx._setFont(f.fontName, f.fontSize)
  288             if xs.textColor != f.textColor:
  289                 xs.textColor = f.textColor
  290                 tx.setFillColor(f.textColor)
  291             if xs.rise != f.rise:
  292                 xs.rise = f.rise
  293                 tx.setRise(f.rise)
  294             text = f.text
  295             tx._textOut(text, f is words[-1])    # cheap textOut
  296 
  297             # XXX Modified for XHTML2PDF
  298             # Background colors (done like underline)
  299             if hasattr(f, "backColor"):
  300                 if xs.backgroundColor != f.backColor or xs.backgroundFontSize != f.fontSize:
  301                     if xs.backgroundColor is not None:
  302                         xs.backgrounds.append((xs.background_x, cur_x_s, xs.backgroundColor, xs.backgroundFontSize))
  303                     xs.background_x = cur_x_s
  304                     xs.backgroundColor = f.backColor
  305                     xs.backgroundFontSize = f.fontSize
  306 
  307             # Underline
  308             if not (hasattr(xs, 'underline') and xs.underline) \
  309                     and (hasattr(f, 'underline') and f.underline):
  310                 xs.underline = 1
  311                 xs.underline_x = cur_x_s
  312                 xs.underlineColor = f.textColor
  313             elif xs.underline:
  314                 if not f.underline:
  315                     xs.underline = 0
  316                     xs.underlines.append((xs.underline_x, cur_x_s, xs.underlineColor))
  317                     xs.underlineColor = None
  318                 elif xs.textColor != xs.underlineColor:
  319                     xs.underlines.append((xs.underline_x, cur_x_s, xs.underlineColor))
  320                     xs.underlineColor = xs.textColor
  321                     xs.underline_x = cur_x_s
  322 
  323             # Strike
  324             if not (hasattr(xs, 'strike') and xs.strike) \
  325                     and (hasattr(f, 'strike') and f.strike):
  326                 xs.strike = 1
  327                 xs.strike_x = cur_x_s
  328                 xs.strikeColor = f.textColor
  329                 # XXX Modified for XHTML2PDF
  330                 xs.strikeFontSize = f.fontSize
  331             elif xs.strike:
  332                 if not f.strike:
  333                     xs.strike = 0
  334                     # XXX Modified for XHTML2PDF
  335                     xs.strikes.append((xs.strike_x, cur_x_s, xs.strikeColor, xs.strikeFontSize))
  336                     xs.strikeColor = None
  337                     xs.strikeFontSize = None
  338                 elif xs.textColor != xs.strikeColor:
  339                     xs.strikes.append((xs.strike_x, cur_x_s, xs.strikeColor, xs.strikeFontSize))
  340                     xs.strikeColor = xs.textColor
  341                     xs.strikeFontSize = f.fontSize
  342                     xs.strike_x = cur_x_s
  343             if f.link and not xs.link:
  344                 if not xs.link:
  345                     xs.link = f.link
  346                     xs.link_x = cur_x_s
  347                     xs.linkColor = xs.textColor
  348             elif xs.link:
  349                 if not f.link:
  350                     xs.links.append((xs.link_x, cur_x_s, xs.link, xs.linkColor))
  351                     xs.link = None
  352                     xs.linkColor = None
  353                 elif f.link != xs.link or xs.textColor != xs.linkColor:
  354                     xs.links.append((xs.link_x, cur_x_s, xs.link, xs.linkColor))
  355                     xs.link = f.link
  356                     xs.link_x = cur_x_s
  357                     xs.linkColor = xs.textColor
  358             txtlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize)
  359             cur_x += txtlen
  360             try:
  361                 nSpaces += text.count(' ')
  362             except Exception:
  363                 nSpaces += text.decode("utf8").count(' ')
  364     cur_x_s = cur_x + (nSpaces - 1) * ws
  365 
  366     # XXX Modified for XHTML2PDF
  367     # Underline
  368     if xs.underline:
  369         xs.underlines.append((xs.underline_x, cur_x_s, xs.underlineColor))
  370 
  371     # XXX Modified for XHTML2PDF
  372     # Backcolor
  373     if hasattr(f, "backColor"):
  374         if xs.backgroundColor is not None:
  375             xs.backgrounds.append((xs.background_x, cur_x_s, xs.backgroundColor, xs.backgroundFontSize))
  376 
  377     # XXX Modified for XHTML2PDF
  378     # Strike
  379     if xs.strike:
  380         xs.strikes.append((xs.strike_x, cur_x_s, xs.strikeColor, xs.strikeFontSize))
  381 
  382     if xs.link:
  383         xs.links.append((xs.link_x, cur_x_s, xs.link, xs.linkColor))
  384     if tx._x0 != x0:
  385         setXPos(tx, x0 - tx._x0)
  386 
  387 
  388 def _leftDrawParaLineX( tx, offset, line, last=0):
  389     setXPos(tx, offset)
  390     _putFragLine(offset, tx, line)
  391     setXPos(tx, -offset)
  392 
  393 
  394 def _centerDrawParaLineX( tx, offset, line, last=0):
  395     m = offset + 0.5 * line.extraSpace
  396     setXPos(tx, m)
  397     _putFragLine(m, tx, line)
  398     setXPos(tx, -m)
  399 
  400 
  401 def _rightDrawParaLineX( tx, offset, line, last=0):
  402     m = offset + line.extraSpace
  403     setXPos(tx, m)
  404     _putFragLine(m, tx, line)
  405     setXPos(tx, -m)
  406 
  407 
  408 def _justifyDrawParaLineX( tx, offset, line, last=0):
  409     setXPos(tx, offset)
  410     extraSpace = line.extraSpace
  411     nSpaces = line.wordCount - 1
  412     if last or not nSpaces or abs(extraSpace) <= 1e-8 or line.lineBreak:
  413         _putFragLine(offset, tx, line)  # no space modification
  414     else:
  415         tx.setWordSpace(extraSpace / float(nSpaces))
  416         _putFragLine(offset, tx, line)
  417         tx.setWordSpace(0)
  418     setXPos(tx, -offset)
  419 
  420 
  421 def _sameFrag(f, g):
  422     """
  423     returns 1 if two ParaFrags map out the same
  424     """
  425 
  426     if (hasattr(f, 'cbDefn') or hasattr(g, 'cbDefn')
  427         or hasattr(f, 'lineBreak') or hasattr(g, 'lineBreak')): return 0
  428     for a in ('fontName', 'fontSize', 'textColor', 'backColor', 'rise', 'underline', 'strike', 'link'):
  429         if getattr(f, a, None) != getattr(g, a, None): return 0
  430     return 1
  431 
  432 
  433 def _getFragWords(frags):
  434     """
  435     given a Parafrag list return a list of fragwords
  436         [[size, (f00,w00), ..., (f0n,w0n)],....,[size, (fm0,wm0), ..., (f0n,wmn)]]
  437         each pair f,w represents a style and some string
  438         each sublist represents a word
  439     """
  440     R = []
  441     W = []
  442     n = 0
  443     hangingStrip = False
  444     for f in frags:
  445         text = f.text
  446         if type(text) is bytes:
  447             text = text.decode('utf8')
  448         # of paragraphs
  449         if text != '':
  450             if hangingStrip:
  451                 hangingStrip = False
  452                 text = text.lstrip()
  453 
  454             S = split(text)
  455             if S == []:
  456                 S = ['']
  457             if W != [] and text[0] in whitespace:
  458                 W.insert(0, n)
  459                 R.append(W)
  460                 W = []
  461                 n = 0
  462 
  463             for w in S[:-1]:
  464                 W.append((f, w))
  465                 n += stringWidth(w, f.fontName, f.fontSize)
  466                 W.insert(0, n)
  467                 R.append(W)
  468                 W = []
  469                 n = 0
  470 
  471             w = S[-1]
  472             W.append((f, w))
  473             n += stringWidth(w, f.fontName, f.fontSize)
  474             if text and text[-1] in whitespace:
  475                 W.insert(0, n)
  476                 R.append(W)
  477                 W = []
  478                 n = 0
  479         elif hasattr(f, 'cbDefn'):
  480             w = getattr(f.cbDefn, 'width', 0)
  481             if w:
  482                 if W != []:
  483                     W.insert(0, n)
  484                     R.append(W)
  485                     W = []
  486                     n = 0
  487                 R.append([w, (f, '')])
  488             else:
  489                 W.append((f, ''))
  490         elif hasattr(f, 'lineBreak'):
  491             #pass the frag through.  The line breaker will scan for it.
  492             if W != []:
  493                 W.insert(0, n)
  494                 R.append(W)
  495                 W = []
  496                 n = 0
  497             R.append([0, (f, '')])
  498             hangingStrip = True
  499 
  500     if W != []:
  501         W.insert(0, n)
  502         R.append(W)
  503 
  504     return R
  505 
  506 
  507 def _split_blParaSimple(blPara, start, stop):
  508     f = blPara.clone()
  509     for a in ('lines', 'kind', 'text'):
  510         if hasattr(f, a): delattr(f, a)
  511 
  512     f.words = []
  513     for l in blPara.lines[start:stop]:
  514         for w in l[1]:
  515             f.words.append(w)
  516     return [f]
  517 
  518 
  519 def _split_blParaHard(blPara, start, stop):
  520     f = []
  521     lines = blPara.lines[start:stop]
  522     for l in lines:
  523         for w in l.words:
  524             f.append(w)
  525         if l is not lines[-1]:
  526             i = len(f) - 1
  527             while i >= 0 and hasattr(f[i], 'cbDefn') and not getattr(f[i].cbDefn, 'width', 0): i -= 1
  528             if i >= 0:
  529                 g = f[i]
  530                 if not g.text:
  531                     g.text = ' '
  532                 else:
  533                     if type(g.text) is bytes:
  534                         g.text = g.text.decode('utf8')
  535                     if g.text[-1] != ' ':
  536                         g.text += ' '
  537     return f
  538 
  539 
  540 def _drawBullet(canvas, offset, cur_y, bulletText, style):
  541     """
  542     draw a bullet text could be a simple string or a frag list
  543     """
  544 
  545     tx2 = canvas.beginText(style.bulletIndent, cur_y + getattr(style, "bulletOffsetY", 0))
  546     tx2.setFont(style.bulletFontName, style.bulletFontSize)
  547     tx2.setFillColor(hasattr(style, 'bulletColor') and style.bulletColor or style.textColor)
  548     if isinstance(bulletText, basestring):
  549         tx2.textOut(bulletText)
  550     else:
  551         for f in bulletText:
  552             if hasattr(f, "image"):
  553                 image = f.image
  554                 width = image.drawWidth
  555                 height = image.drawHeight
  556                 gap = style.bulletFontSize * 0.25
  557                 img = image.getImage()
  558                 # print style.bulletIndent, offset, width
  559                 canvas.drawImage(
  560                     img,
  561                     style.leftIndent - width - gap,
  562                     cur_y + getattr(style, "bulletOffsetY", 0),
  563                     width,
  564                     height)
  565             else:
  566                 tx2.setFont(f.fontName, f.fontSize)
  567                 tx2.setFillColor(f.textColor)
  568                 tx2.textOut(f.text)
  569     canvas.drawText(tx2)
  570     #AR making definition lists a bit less ugly
  571     #bulletEnd = tx2.getX()
  572     bulletEnd = tx2.getX() + style.bulletFontSize * 0.6
  573     offset = max(offset, bulletEnd - style.leftIndent)
  574     return offset
  575 
  576 
  577 def _handleBulletWidth(bulletText, style, maxWidths):
  578     """
  579     work out bullet width and adjust maxWidths[0] if neccessary
  580     """
  581     if bulletText:
  582         if isinstance(bulletText, basestring):
  583             bulletWidth = stringWidth(bulletText, style.bulletFontName, style.bulletFontSize)
  584         else:
  585             #it's a list of fragments
  586             bulletWidth = 0
  587             for f in bulletText:
  588                 bulletWidth = bulletWidth + stringWidth(f.text, f.fontName, f.fontSize)
  589         bulletRight = style.bulletIndent + bulletWidth + 0.6 * style.bulletFontSize
  590         indent = style.leftIndent + style.firstLineIndent
  591         if bulletRight > indent:
  592             #..then it overruns, and we have less space available on line 1
  593             maxWidths[0] -= (bulletRight - indent)
  594 
  595 
  596 def splitLines0(frags, widths):
  597     """
  598     given a list of ParaFrags we return a list of ParaLines
  599 
  600     each ParaLine has
  601     1)  ExtraSpace
  602     2)  blankCount
  603     3)  [textDefns....]
  604         each text definition is a (ParaFrag, start, limit) triplet
  605     """
  606 
  607     #initialise the algorithm
  608     lineNum = 0
  609     maxW = widths[lineNum]
  610     i = -1
  611     l = len(frags)
  612     lim = start = 0
  613     text = frags[0]
  614     while 1:
  615         #find a non whitespace character
  616         while i < l:
  617             while start < lim and text[start] == ' ': start += 1
  618             if start == lim:
  619                 i += 1
  620                 if i == l: break
  621                 start = 0
  622                 f = frags[i]
  623                 text = f.text
  624                 lim = len(text)
  625             else:
  626                 break   # we found one
  627 
  628         if start == lim: break    # if we didn't find one we are done
  629 
  630         #start of a line
  631         g = (None, None, None)
  632         line = []
  633         cLen = 0
  634         nSpaces = 0
  635         while cLen < maxW:
  636             j = text.find(' ', start)
  637             if j < 0:
  638                 j == lim
  639             w = stringWidth(text[start:j], f.fontName, f.fontSize)
  640             cLen += w
  641             if cLen > maxW and line != []:
  642                 cLen = cLen - w
  643                 #this is the end of the line
  644                 while g.text[lim] == ' ':
  645                     lim -= 1
  646                     nSpaces -= 1
  647                 break
  648             if j < 0:
  649                 j = lim
  650             if g[0] is f:
  651                 g[2] = j  #extend
  652             else:
  653                 g = (f, start, j)
  654                 line.append(g)
  655             if j == lim:
  656                 i += 1
  657 
  658 
  659 def _do_under_line(i, t_off, ws, tx, lm=-0.125):
  660     y = tx.XtraState.cur_y - i * tx.XtraState.style.leading + lm * tx.XtraState.f.fontSize
  661     textlen = tx._canvas.stringWidth(b" ".join(tx.XtraState.lines[i][1]), tx._fontname, tx._fontsize)
  662     tx._canvas.line(t_off, y, t_off + textlen + ws, y)
  663 
  664 
  665 _scheme_re = re.compile('^[a-zA-Z][-+a-zA-Z0-9]+$')
  666 
  667 
  668 def _doLink(tx, link, rect):
  669     if six.PY2:
  670         link = six.text_type(link, 'utf8') 
  671     parts = link.split(':', 1)
  672     scheme = len(parts) == 2 and parts[0].lower() or ''
  673     if _scheme_re.match(scheme) and scheme != 'document':
  674         kind = scheme.lower() == 'pdf' and 'GoToR' or 'URI'
  675         if kind == 'GoToR': link = parts[1]
  676         tx._canvas.linkURL(link, rect, relative=1, kind=kind)
  677     else:
  678         if link[0] == '#':
  679             link = link[1:]
  680             scheme = ''
  681         tx._canvas.linkRect("", scheme != 'document' and link or parts[1], rect, relative=1)
  682 
  683 
  684 def _do_link_line(i, t_off, ws, tx):
  685     xs = tx.XtraState
  686     leading = xs.style.leading
  687     y = xs.cur_y - i * leading - xs.f.fontSize / 8.0 # 8.0 factor copied from para.py
  688     text = b" ".join(xs.lines[i][1])
  689     textlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize)
  690     _doLink(tx, xs.link, (t_off, y, t_off + textlen + ws, y + leading))
  691 
  692 
  693 # XXX Modified for XHTML2PDF
  694 def _do_post_text(tx):
  695     """
  696     Try to find out what the variables mean:
  697 
  698     tx         A structure containing more informations about paragraph ???
  699 
  700     leading    Height of lines
  701     ff         1/8 of the font size
  702     y0         The "baseline" postion ???
  703     y          1/8 below the baseline
  704     """
  705 
  706     xs = tx.XtraState
  707     leading = xs.style.leading
  708     autoLeading = xs.autoLeading
  709     f = xs.f
  710     if autoLeading == 'max':
  711         # leading = max(leading, f.fontSize)
  712         leading = max(leading, LEADING_FACTOR * f.fontSize)
  713     elif autoLeading == 'min':
  714         leading = LEADING_FACTOR * f.fontSize
  715     ff = 0.125 * f.fontSize
  716     y0 = xs.cur_y
  717     y = y0 - ff
  718 
  719     # Background
  720     for x1, x2, c, fs in xs.backgrounds:
  721         inlineFF = fs * 0.125
  722         gap = inlineFF * 1.25
  723         tx._canvas.setFillColor(c)
  724         tx._canvas.rect(x1, y - gap, x2 - x1, fs + 1, fill=1, stroke=0)
  725     xs.backgrounds = []
  726     xs.background = 0
  727     xs.backgroundColor = None
  728     xs.backgroundFontSize = None
  729 
  730     # Underline
  731     yUnderline = y0 - 1.5 * ff
  732     tx._canvas.setLineWidth(ff * 0.75)
  733     csc = None
  734     for x1, x2, c in xs.underlines:
  735         if c != csc:
  736             tx._canvas.setStrokeColor(c)
  737             csc = c
  738         tx._canvas.line(x1, yUnderline, x2, yUnderline)
  739     xs.underlines = []
  740     xs.underline = 0
  741     xs.underlineColor = None
  742 
  743     # Strike
  744     for x1, x2, c, fs in xs.strikes:
  745         inlineFF = fs * 0.125
  746         ys = y0 + 2 * inlineFF
  747         if c != csc:
  748             tx._canvas.setStrokeColor(c)
  749             csc = c
  750         tx._canvas.setLineWidth(inlineFF * 0.75)
  751         tx._canvas.line(x1, ys, x2, ys)
  752     xs.strikes = []
  753     xs.strike = 0
  754     xs.strikeColor = None
  755 
  756     yl = y + leading
  757     for x1, x2, link, c in xs.links:
  758         # No automatic underlining for links, never!
  759         _doLink(tx, link, (x1, y, x2, yl))
  760     xs.links = []
  761     xs.link = None
  762     xs.linkColor = None
  763     xs.cur_y -= leading
  764 
  765 
  766 def textTransformFrags(frags, style):
  767     tt = style.textTransform
  768     if tt:
  769         tt = tt.lower()
  770         if tt == 'lowercase':
  771             tt = unicode.lower
  772         elif tt == 'uppercase':
  773             tt = unicode.upper
  774         elif tt == 'capitalize':
  775             tt = unicode.title
  776         elif tt == 'none':
  777             return
  778         else:
  779             raise ValueError('ParaStyle.textTransform value %r is invalid' % style.textTransform)
  780         n = len(frags)
  781         if n == 1:
  782             #single fragment the easy case
  783             frags[0].text = tt(frags[0].text.decode('utf8')).encode('utf8')
  784         elif tt is unicode.title:
  785             pb = True
  786             for f in frags:
  787                 t = f.text
  788                 if not t: continue
  789                 u = t.decode('utf8')
  790                 if u.startswith(u' ') or pb:
  791                     u = tt(u)
  792                 else:
  793                     i = u.find(u' ')
  794                     if i >= 0:
  795                         u = u[:i] + tt(u[i:])
  796                 pb = u.endswith(u' ')
  797                 f.text = u.encode('utf8')
  798         else:
  799             for f in frags:
  800                 t = f.text
  801                 if not t: continue
  802                 f.text = tt(t.decode('utf8')).encode('utf8')
  803 
  804 
  805 class cjkU(unicode):
  806     """
  807     simple class to hold the frag corresponding to a str
  808     """
  809 
  810     def __new__(cls, value, frag, encoding):
  811         self = unicode.__new__(cls, value)
  812         self._frag = frag
  813         if hasattr(frag, 'cbDefn'):
  814             w = getattr(frag.cbDefn, 'width', 0)
  815             self._width = w
  816         else:
  817             self._width = stringWidth(value, frag.fontName, frag.fontSize)
  818         return self
  819 
  820     frag = property(lambda self: self._frag)
  821     width = property(lambda self: self._width)
  822 
  823 
  824 def makeCJKParaLine(U, extraSpace, calcBounds):
  825     words = []
  826     CW = []
  827     f0 = FragLine()
  828     maxSize = maxAscent = minDescent = 0
  829     for u in U:
  830         f = u.frag
  831         fontSize = f.fontSize
  832         if calcBounds:
  833             cbDefn = getattr(f, 'cbDefn', None)
  834             if getattr(cbDefn, 'width', 0):
  835                 descent, ascent = imgVRange(cbDefn.height, cbDefn.valign, fontSize)
  836             else:
  837                 ascent, descent = getAscentDescent(f.fontName, fontSize)
  838         else:
  839             ascent, descent = getAscentDescent(f.fontName, fontSize)
  840         maxSize = max(maxSize, fontSize)
  841         maxAscent = max(maxAscent, ascent)
  842         minDescent = min(minDescent, descent)
  843         if not _sameFrag(f0, f):
  844             f0 = f0.clone()
  845             f0.text = u''.join(CW)
  846             words.append(f0)
  847             CW = []
  848             f0 = f
  849         CW.append(u)
  850     if CW:
  851         f0 = f0.clone()
  852         f0.text = u''.join(CW)
  853         words.append(f0)
  854     return FragLine(kind=1, extraSpace=extraSpace, wordCount=1, words=words[1:], fontSize=maxSize, ascent=maxAscent,
  855                     descent=minDescent)
  856 
  857 
  858 def cjkFragSplit(frags, maxWidths, calcBounds, encoding='utf8'):
  859     """
  860     This attempts to be wordSplit for frags using the dumb algorithm
  861     """
  862 
  863     from reportlab.rl_config import _FUZZ
  864 
  865     U = []  # get a list of single glyphs with their widths etc etc
  866     for f in frags:
  867         text = f.text
  868         if not isinstance(text, unicode):
  869             text = text.decode(encoding)
  870         if text:
  871             U.extend([cjkU(t, f, encoding) for t in text])
  872         else:
  873             U.append(cjkU(text, f, encoding))
  874 
  875     lines = []
  876     widthUsed = lineStartPos = 0
  877     maxWidth = maxWidths[0]
  878 
  879     for i, u in enumerate(U):
  880         w = u.width
  881         widthUsed += w
  882         lineBreak = hasattr(u.frag, 'lineBreak')
  883         endLine = (widthUsed > maxWidth + _FUZZ and widthUsed > 0) or lineBreak
  884         if endLine:
  885             if lineBreak: continue
  886             extraSpace = maxWidth - widthUsed + w
  887             #This is the most important of the Japanese typography rules.
  888             #if next character cannot start a line, wrap it up to this line so it hangs
  889             #in the right margin. We won't do two or more though - that's unlikely and
  890             #would result in growing ugliness.
  891             nextChar = U[i]
  892             if nextChar in ALL_CANNOT_START:
  893                 extraSpace -= w
  894                 i += 1
  895             lines.append(makeCJKParaLine(U[lineStartPos:i], extraSpace, calcBounds))
  896             try:
  897                 maxWidth = maxWidths[len(lines)]
  898             except IndexError:
  899                 maxWidth = maxWidths[-1]  # use the last one
  900 
  901             lineStartPos = i
  902             widthUsed = w
  903             i -= 1
  904         #any characters left?
  905     if widthUsed > 0:
  906         lines.append(makeCJKParaLine(U[lineStartPos:], maxWidth - widthUsed, calcBounds))
  907 
  908     return ParaLines(kind=1, lines=lines)
  909 
  910 
  911 class Paragraph(Flowable):
  912     """
  913     Paragraph(text, style, bulletText=None, caseSensitive=1)
  914         text a string of stuff to go into the paragraph.
  915         style is a style definition as in reportlab.lib.styles.
  916         bulletText is an optional bullet defintion.
  917         caseSensitive set this to 0 if you want the markup tags and their attributes to be case-insensitive.
  918 
  919         This class is a flowable that can format a block of text
  920         into a paragraph with a given style.
  921 
  922         The paragraph Text can contain XML-like markup including the tags:
  923         <b> ... </b> - bold
  924         <i> ... </i> - italics
  925         <u> ... </u> - underline
  926         <strike> ... </strike> - strike through
  927         <super> ... </super> - superscript
  928         <sub> ... </sub> - subscript
  929         <font name=fontfamily/fontname color=colorname size=float>
  930         <onDraw name=callable label="a label">
  931         <link>link text</link>
  932             attributes of links
  933                 size/fontSize=num
  934                 name/face/fontName=name
  935                 fg/textColor/color=color
  936                 backcolor/backColor/bgcolor=color
  937                 dest/destination/target/href/link=target
  938         <a>anchor text</a>
  939             attributes of anchors
  940                 fontSize=num
  941                 fontName=name
  942                 fg/textColor/color=color
  943                 backcolor/backColor/bgcolor=color
  944                 href=href
  945         <a name="anchorpoint"/>
  946         <unichar name="unicode character name"/>
  947         <unichar value="unicode code point"/>
  948         <img src="path" width="1in" height="1in" valign="bottom"/>
  949 
  950         The whole may be surrounded by <para> </para> tags
  951 
  952         The <b> and <i> tags will work for the built-in fonts (Helvetica
  953         /Times / Courier).  For other fonts you need to register a family
  954         of 4 fonts using reportlab.pdfbase.pdfmetrics.registerFont; then
  955         use the addMapping function to tell the library that these 4 fonts
  956         form a family e.g.
  957             from reportlab.lib.fonts import addMapping
  958             addMapping('Vera', 0, 0, 'Vera')    #normal
  959             addMapping('Vera', 0, 1, 'Vera-Italic')    #italic
  960             addMapping('Vera', 1, 0, 'Vera-Bold')    #bold
  961             addMapping('Vera', 1, 1, 'Vera-BoldItalic')    #italic and bold
  962 
  963         It will also be able to handle any MathML specified Greek characters.
  964     """
  965     def __init__(self, text, style, bulletText=None, frags=None, caseSensitive=1, encoding='utf8'):
  966         self.caseSensitive = caseSensitive
  967         self.encoding = encoding
  968         self._setup(text, style, bulletText, frags, cleanBlockQuotedText)
  969 
  970     def __repr__(self):
  971         n = self.__class__.__name__
  972         L = [n + "("]
  973         keys = self.__dict__.keys()
  974         for k in keys:
  975             v = getattr(self, k)
  976             rk = repr(k)
  977             rv = repr(v)
  978             rk = "  " + rk.replace("\n", "\n  ")
  979             rv = "    " + rk.replace("\n", "\n    ")
  980             L.append(rk)
  981             L.append(rv)
  982         L.append(") #" + n)
  983         return '\n'.join(L)
  984 
  985     def _setup(self, text, style, bulletText, frags, cleaner):
  986         if frags is None:
  987             text = cleaner(text)
  988             _parser.caseSensitive = self.caseSensitive
  989             style, frags, bulletTextFrags = _parser.parse(text, style)
  990             if frags is None:
  991                 raise ValueError("xml parser error (%s) in paragraph beginning\n'%s'" \
  992                                  % (_parser.errors[0], text[:min(30, len(text))]))
  993             textTransformFrags(frags, style)
  994             if bulletTextFrags: bulletText = bulletTextFrags
  995 
  996         #AR hack
  997         self.text = text
  998         self.frags = frags
  999         self.style = style
 1000         self.bulletText = bulletText
 1001         self.debug = PARAGRAPH_DEBUG  # turn this on to see a pretty one with all the margins etc.
 1002 
 1003     def wrap(self, availWidth, availHeight):
 1004 
 1005         if self.debug:
 1006             print (id(self), "wrap")
 1007             try:
 1008                 print (repr(self.getPlainText()[:80]))
 1009             except:
 1010                 print ("???")
 1011 
 1012         # work out widths array for breaking
 1013         self.width = availWidth
 1014         style = self.style
 1015         leftIndent = style.leftIndent
 1016         first_line_width = availWidth - (leftIndent + style.firstLineIndent) - style.rightIndent
 1017         later_widths = availWidth - leftIndent - style.rightIndent
 1018 
 1019         if style.wordWrap == 'CJK':
 1020             #use Asian text wrap algorithm to break characters
 1021             blPara = self.breakLinesCJK([first_line_width, later_widths])
 1022         else:
 1023             blPara = self.breakLines([first_line_width, later_widths])
 1024         self.blPara = blPara
 1025         autoLeading = getattr(self, 'autoLeading', getattr(style, 'autoLeading', ''))
 1026         leading = style.leading
 1027         if blPara.kind == 1 and autoLeading not in ('', 'off'):
 1028             height = 0
 1029             if autoLeading == 'max':
 1030                 for l in blPara.lines:
 1031                     height += max(l.ascent - l.descent, leading)
 1032             elif autoLeading == 'min':
 1033                 for l in blPara.lines:
 1034                     height += l.ascent - l.descent
 1035             else:
 1036                 raise ValueError('invalid autoLeading value %r' % autoLeading)
 1037         else:
 1038             if autoLeading == 'max':
 1039                 leading = max(leading, LEADING_FACTOR * style.fontSize)
 1040             elif autoLeading == 'min':
 1041                 leading = LEADING_FACTOR * style.fontSize
 1042             height = len(blPara.lines) * leading
 1043         self.height = height
 1044 
 1045         return self.width, height
 1046 
 1047     def minWidth(self):
 1048         """
 1049         Attempt to determine a minimum sensible width
 1050         """
 1051 
 1052         frags = self.frags
 1053         nFrags = len(frags)
 1054         if not nFrags: return 0
 1055         if nFrags == 1:
 1056             f = frags[0]
 1057             fS = f.fontSize
 1058             fN = f.fontName
 1059             words = hasattr(f, 'text') and split(f.text, ' ') or f.words
 1060             func = lambda w, fS=fS, fN=fN: stringWidth(w, fN, fS)
 1061         else:
 1062             words = _getFragWords(frags)
 1063             func = lambda x: x[0]
 1064         return max(map(func, words))
 1065 
 1066     def _get_split_blParaFunc(self):
 1067         return self.blPara.kind == 0 and _split_blParaSimple or _split_blParaHard
 1068 
 1069     def split(self, availWidth, availHeight):
 1070 
 1071         if self.debug:
 1072             print  (id(self), "split")
 1073 
 1074         if len(self.frags) <= 0: return []
 1075 
 1076         #the split information is all inside self.blPara
 1077         if not hasattr(self, 'blPara'):
 1078             self.wrap(availWidth, availHeight)
 1079 
 1080         blPara = self.blPara
 1081         style = self.style
 1082         autoLeading = getattr(self, 'autoLeading', getattr(style, 'autoLeading', ''))
 1083         leading = style.leading
 1084         lines = blPara.lines
 1085         if blPara.kind == 1 and autoLeading not in ('', 'off'):
 1086             s = height = 0
 1087             if autoLeading == 'max':
 1088                 for i, l in enumerate(blPara.lines):
 1089                     h = max(l.ascent - l.descent, leading)
 1090                     n = height + h
 1091                     if n > availHeight + 1e-8:
 1092                         break
 1093                     height = n
 1094                     s = i + 1
 1095             elif autoLeading == 'min':
 1096                 for i, l in enumerate(blPara.lines):
 1097                     n = height + l.ascent - l.descent
 1098                     if n > availHeight + 1e-8:
 1099                         break
 1100                     height = n
 1101                     s = i + 1
 1102             else:
 1103                 raise ValueError('invalid autoLeading value %r' % autoLeading)
 1104         else:
 1105             l = leading
 1106             if autoLeading == 'max':
 1107                 l = max(leading, LEADING_FACTOR * style.fontSize)
 1108             elif autoLeading == 'min':
 1109                 l = LEADING_FACTOR * style.fontSize
 1110             s = int(availHeight / l)
 1111             height = s * l
 1112 
 1113         n = len(lines)
 1114         allowWidows = getattr(self, 'allowWidows', getattr(self, 'allowWidows', 1))
 1115         allowOrphans = getattr(self, 'allowOrphans', getattr(self, 'allowOrphans', 0))
 1116         if not allowOrphans:
 1117             if s <= 1:    # orphan?
 1118                 del self.blPara
 1119                 return []
 1120         if n <= s: return [self]
 1121         if not allowWidows:
 1122             if n == s + 1: # widow?
 1123                 if (allowOrphans and n == 3) or n > 3:
 1124                     s -= 1  # give the widow some company
 1125                 else:
 1126                     del self.blPara # no room for adjustment; force the whole para onwards
 1127                     return []
 1128         func = self._get_split_blParaFunc()
 1129 
 1130         P1 = self.__class__(None, style, bulletText=self.bulletText, frags=func(blPara, 0, s))
 1131         #this is a major hack
 1132         P1.blPara = ParaLines(kind=1, lines=blPara.lines[0:s], aH=availHeight, aW=availWidth)
 1133         P1._JustifyLast = 1
 1134         P1._splitpara = 1
 1135         P1.height = height
 1136         P1.width = availWidth
 1137         if style.firstLineIndent != 0:
 1138             style = deepcopy(style)
 1139             style.firstLineIndent = 0
 1140         P2 = self.__class__(None, style, bulletText=None, frags=func(blPara, s, n))
 1141         for a in ('autoLeading',    # possible attributes that might be directly on self.
 1142         ):
 1143             if hasattr(self, a):
 1144                 setattr(P1, a, getattr(self, a))
 1145                 setattr(P2, a, getattr(self, a))
 1146         return [P1, P2]
 1147 
 1148     def draw(self):
 1149         #call another method for historical reasons.  Besides, I
 1150         #suspect I will be playing with alternate drawing routines
 1151         #so not doing it here makes it easier to switch.
 1152         self.drawPara(self.debug)
 1153 
 1154     def breakLines(self, width):
 1155         """
 1156         Returns a broken line structure. There are two cases
 1157 
 1158         A) For the simple case of a single formatting input fragment the output is
 1159             A fragment specifier with
 1160                 - kind = 0
 1161                 - fontName, fontSize, leading, textColor
 1162                 - lines=  A list of lines
 1163 
 1164                         Each line has two items.
 1165 
 1166                         1. unused width in points
 1167                         2. word list
 1168 
 1169         B) When there is more than one input formatting fragment the output is
 1170             A fragment specifier with
 1171                - kind = 1
 1172                - lines=  A list of fragments each having fields
 1173                             - extraspace (needed for justified)
 1174                             - fontSize
 1175                             - words=word list
 1176                                 each word is itself a fragment with
 1177                                 various settings
 1178 
 1179         This structure can be used to easily draw paragraphs with the various alignments.
 1180         You can supply either a single width or a list of widths; the latter will have its
 1181         last item repeated until necessary. A 2-element list is useful when there is a
 1182         different first line indent; a longer list could be created to facilitate custom wraps
 1183         around irregular objects.
 1184         """
 1185 
 1186         if self.debug:
 1187             print (id(self), "breakLines")
 1188 
 1189         if not isinstance(width, (tuple, list)):
 1190             maxWidths = [width]
 1191         else:
 1192             maxWidths = width
 1193         lines = []
 1194         lineno = 0
 1195         style = self.style
 1196 
 1197         #for bullets, work out width and ensure we wrap the right amount onto line one
 1198         _handleBulletWidth(self.bulletText, style, maxWidths)
 1199 
 1200         maxWidth = maxWidths[0]
 1201 
 1202         self.height = 0
 1203         autoLeading = getattr(self, 'autoLeading', getattr(style, 'autoLeading', ''))
 1204         calcBounds = autoLeading not in ('', 'off')
 1205         frags = self.frags
 1206         nFrags = len(frags)
 1207         if nFrags == 1 and not hasattr(frags[0], 'cbDefn'):
 1208             f = frags[0]
 1209             fontSize = f.fontSize
 1210             fontName = f.fontName
 1211             ascent, descent = getAscentDescent(fontName, fontSize)
 1212             words = hasattr(f, 'text') and split(f.text, ' ') or f.words
 1213             spaceWidth = stringWidth(' ', fontName, fontSize, self.encoding)
 1214             cLine = []
 1215             currentWidth = -spaceWidth   # hack to get around extra space for word 1
 1216             for word in words:
 1217                 #this underscores my feeling that Unicode throughout would be easier!
 1218                 wordWidth = stringWidth(word, fontName, fontSize, self.encoding)
 1219                 newWidth = currentWidth + spaceWidth + wordWidth
 1220                 if newWidth <= maxWidth or not len(cLine):
 1221                     # fit one more on this line
 1222                     cLine.append(word)
 1223                     currentWidth = newWidth
 1224                 else:
 1225                     if currentWidth > self.width: self.width = currentWidth
 1226                     #end of line
 1227                     lines.append((maxWidth - currentWidth, cLine))
 1228                     cLine = [word]
 1229                     currentWidth = wordWidth
 1230                     lineno += 1
 1231                     try:
 1232                         maxWidth = maxWidths[lineno]
 1233                     except IndexError:
 1234                         maxWidth = maxWidths[-1]  # use the last one
 1235 
 1236             #deal with any leftovers on the final line
 1237             if cLine != []:
 1238                 if currentWidth > self.width: self.width = currentWidth
 1239                 lines.append((maxWidth - currentWidth, cLine))
 1240 
 1241             return f.clone(kind=0, lines=lines, ascent=ascent, descent=descent, fontSize=fontSize)
 1242         elif nFrags <= 0:
 1243             return ParaLines(kind=0, fontSize=style.fontSize, fontName=style.fontName,
 1244                              textColor=style.textColor, ascent=style.fontSize, descent=-0.2 * style.fontSize,
 1245                              lines=[])
 1246         else:
 1247             if hasattr(self, 'blPara') and getattr(self, '_splitpara', 0):
 1248                 #NB this is an utter hack that awaits the proper information
 1249                 #preserving splitting algorithm
 1250                 return self.blPara
 1251             n = 0
 1252             words = []
 1253             for w in _getFragWords(frags):
 1254                 f = w[-1][0]
 1255                 fontName = f.fontName
 1256                 fontSize = f.fontSize
 1257                 spaceWidth = stringWidth(' ', fontName, fontSize)
 1258 
 1259                 if not words:
 1260                     currentWidth = -spaceWidth   # hack to get around extra space for word 1
 1261                     maxSize = fontSize
 1262                     maxAscent, minDescent = getAscentDescent(fontName, fontSize)
 1263 
 1264                 wordWidth = w[0]
 1265                 f = w[1][0]
 1266                 if wordWidth > 0:
 1267                     newWidth = currentWidth + spaceWidth + wordWidth
 1268                 else:
 1269                     newWidth = currentWidth
 1270 
 1271                 #test to see if this frag is a line break. If it is we will only act on it
 1272                 #if the current width is non-negative or the previous thing was a deliberate lineBreak
 1273                 lineBreak = hasattr(f, 'lineBreak')
 1274                 endLine = (newWidth > maxWidth and n > 0) or lineBreak
 1275                 if not endLine:
 1276                     if lineBreak: continue      #throw it away
 1277                     if type(w[1][1]) != six.text_type:
 1278                         nText = six.text_type(w[1][1], 'utf-8')
 1279                     else:
 1280                         nText = w[1][1]
 1281                         
 1282                     if nText: n += 1
 1283                     fontSize = f.fontSize
 1284                     if calcBounds:
 1285                         cbDefn = getattr(f, 'cbDefn', None)
 1286                         if getattr(cbDefn, 'width', 0):
 1287                             descent, ascent = imgVRange(cbDefn.height, cbDefn.valign, fontSize)
 1288                         else:
 1289                             ascent, descent = getAscentDescent(f.fontName, fontSize)
 1290                     else:
 1291                         ascent, descent = getAscentDescent(f.fontName, fontSize)
 1292                     maxSize = max(maxSize, fontSize)
 1293                     maxAscent = max(maxAscent, ascent)
 1294                     minDescent = min(minDescent, descent)
 1295                     if not words:
 1296                         g = f.clone()
 1297                         words = [g]
 1298                         g.text = nText
 1299                     elif not _sameFrag(g, f):
 1300                         if currentWidth > 0 and ((nText != '' and nText[0] != ' ') or hasattr(f, 'cbDefn')):
 1301                             if hasattr(g, 'cbDefn'):
 1302                                 i = len(words) - 1
 1303                                 while i >= 0:
 1304                                     wi = words[i]
 1305                                     cbDefn = getattr(wi, 'cbDefn', None)
 1306                                     if cbDefn:
 1307                                         if not getattr(cbDefn, 'width', 0):
 1308                                             i -= 1
 1309                                             continue
 1310                                     if not wi.text.endswith(' '):
 1311                                         wi.text += ' '
 1312                                     break
 1313                             else:
 1314                                 if type(g.text) == type(' '):
 1315                                     space = " "
 1316                                 else:
 1317                                     space = b" "
 1318                                 if not g.text.endswith(space):
 1319                                     g.text += space
 1320                         g = f.clone()
 1321                         words.append(g)
 1322                         g.text = nText
 1323                     else:
 1324                         if type(g.text) is bytes:
 1325                             g.text = g.text.decode("utf8")
 1326                         if type(nText) is bytes:
 1327                             nText = nText.decode("utf8")
 1328                         if nText != '' and nText[0] != ' ':
 1329                             g.text += ' ' + nText
 1330 
 1331                     for i in w[2:]:
 1332                         g = i[0].clone()
 1333                         g.text = i[1]
 1334                         words.append(g)
 1335                         fontSize = g.fontSize
 1336                         if calcBounds:
 1337                             cbDefn = getattr(g, 'cbDefn', None)
 1338                             if getattr(cbDefn, 'width', 0):
 1339                                 descent, ascent = imgVRange(cbDefn.height, cbDefn.valign, fontSize)
 1340                             else:
 1341                                 ascent, descent = getAscentDescent(g.fontName, fontSize)
 1342                         else:
 1343                             ascent, descent = getAscentDescent(g.fontName, fontSize)
 1344                         maxSize = max(maxSize, fontSize)
 1345                         maxAscent = max(maxAscent, ascent)
 1346                         minDescent = min(minDescent, descent)
 1347 
 1348                     currentWidth = newWidth
 1349                 else:  # either it won't fit, or it's a lineBreak tag
 1350                     if lineBreak:
 1351                         g = f.clone()
 1352                         words.append(g)
 1353 
 1354                     if currentWidth > self.width: self.width = currentWidth
 1355                     #end of line
 1356                     lines.append(FragLine(extraSpace=maxWidth - currentWidth, wordCount=n,
 1357                                           lineBreak=lineBreak, words=words, fontSize=maxSize, ascent=maxAscent,
 1358                                           descent=minDescent))
 1359 
 1360                     #start new line
 1361                     lineno += 1
 1362                     try:
 1363                         maxWidth = maxWidths[lineno]
 1364                     except IndexError:
 1365                         maxWidth = maxWidths[-1]  # use the last one
 1366 
 1367                     if lineBreak:
 1368                         n = 0
 1369                         words = []
 1370                         continue
 1371 
 1372                     currentWidth = wordWidth
 1373                     n = 1
 1374                     g = f.clone()
 1375                     maxSize = g.fontSize
 1376                     if calcBounds:
 1377                         cbDefn = getattr(g, 'cbDefn', None)
 1378                         if getattr(cbDefn, 'width', 0):
 1379                             minDescent, maxAscent = imgVRange(cbDefn.height, cbDefn.valign, maxSize)
 1380                         else:
 1381                             maxAscent, minDescent = getAscentDescent(g.fontName, maxSize)
 1382                     else:
 1383                         maxAscent, minDescent = getAscentDescent(g.fontName, maxSize)
 1384                     words = [g]
 1385                     g.text = w[1][1]
 1386 
 1387                     for i in w[2:]:
 1388                         g = i[0].clone()
 1389                         g.text = i[1]
 1390                         words.append(g)
 1391                         fontSize = g.fontSize
 1392                         if calcBounds:
 1393                             cbDefn = getattr(g, 'cbDefn', None)
 1394                             if getattr(cbDefn, 'width', 0):
 1395                                 descent, ascent = imgVRange(cbDefn.height, cbDefn.valign, fontSize)
 1396                             else:
 1397                                 ascent, descent = getAscentDescent(g.fontName, fontSize)
 1398                         else:
 1399                             ascent, descent = getAscentDescent(g.fontName, fontSize)
 1400                         maxSize = max(maxSize, fontSize)
 1401                         maxAscent = max(maxAscent, ascent)
 1402                         minDescent = min(minDescent, descent)
 1403 
 1404             #deal with any leftovers on the final line
 1405             if words != []:
 1406                 if currentWidth > self.width: self.width = currentWidth
 1407                 lines.append(ParaLines(extraSpace=(maxWidth - currentWidth), wordCount=n,
 1408                                        words=words, fontSize=maxSize, ascent=maxAscent, descent=minDescent))
 1409             return ParaLines(kind=1, lines=lines)
 1410 
 1411         return lines
 1412 
 1413     def breakLinesCJK(self, width):
 1414         """Initially, the dumbest possible wrapping algorithm.
 1415         Cannot handle font variations."""
 1416 
 1417         if self.debug:
 1418             print (id(self), "breakLinesCJK")
 1419 
 1420         if not isinstance(width, (list, tuple)):
 1421             maxWidths = [width]
 1422         else:
 1423             maxWidths = width
 1424         style = self.style
 1425 
 1426         #for bullets, work out width and ensure we wrap the right amount onto line one
 1427         _handleBulletWidth(self.bulletText, style, maxWidths)
 1428         if len(self.frags) > 1:
 1429             autoLeading = getattr(self, 'autoLeading', getattr(style, 'autoLeading', ''))
 1430             calcBounds = autoLeading not in ('', 'off')
 1431             return cjkFragSplit(self.frags, maxWidths, calcBounds, self.encoding)
 1432 
 1433         elif not len(self.frags):
 1434             return ParaLines(kind=0, fontSize=style.fontSize, fontName=style.fontName,
 1435                              textColor=style.textColor, lines=[], ascent=style.fontSize, descent=-0.2 * style.fontSize)
 1436         f = self.frags[0]
 1437         if 1 and hasattr(self, 'blPara') and getattr(self, '_splitpara', 0):
 1438             #NB this is an utter hack that awaits the proper information
 1439             #preserving splitting algorithm
 1440             return f.clone(kind=0, lines=self.blPara.lines)
 1441         lines = []
 1442 
 1443         self.height = 0
 1444 
 1445         f = self.frags[0]
 1446 
 1447         if hasattr(f, 'text'):
 1448             text = f.text
 1449         else:
 1450             text = ''.join(getattr(f, 'words', []))
 1451 
 1452         from reportlab.lib.textsplit import wordSplit
 1453 
 1454         lines = wordSplit(text, maxWidths[0], f.fontName, f.fontSize)
 1455         #the paragraph drawing routine assumes multiple frags per line, so we need an
 1456         #extra list like this
 1457         #  [space, [text]]
 1458         #
 1459         wrappedLines = [(sp, [line]) for (sp, line) in lines]
 1460         return f.clone(kind=0, lines=wrappedLines, ascent=f.fontSize, descent=-0.2 * f.fontSize)
 1461 
 1462     def beginText(self, x, y):
 1463         return self.canv.beginText(x, y)
 1464 
 1465     def drawPara(self, debug=0):
 1466         """Draws a paragraph according to the given style.
 1467         Returns the final y position at the bottom. Not safe for
 1468         paragraphs without spaces e.g. Japanese; wrapping
 1469         algorithm will go infinite."""
 1470 
 1471         if self.debug:
 1472             print (id(self), "drawPara", self.blPara.kind)
 1473 
 1474         #stash the key facts locally for speed
 1475         canvas = self.canv
 1476         style = self.style
 1477         blPara = self.blPara
 1478         lines = blPara.lines
 1479         leading = style.leading
 1480         autoLeading = getattr(self, 'autoLeading', getattr(style, 'autoLeading', ''))
 1481 
 1482         #work out the origin for line 1
 1483         leftIndent = style.leftIndent
 1484         cur_x = leftIndent
 1485 
 1486         if debug:
 1487             bw = 0.5
 1488             bc = Color(1, 1, 0)
 1489             bg = Color(0.9, 0.9, 0.9)
 1490         else:
 1491             bw = getattr(style, 'borderWidth', None)
 1492             bc = getattr(style, 'borderColor', None)
 1493             bg = style.backColor
 1494 
 1495         #if has a background or border, draw it
 1496         if bg or (bc and bw):
 1497             canvas.saveState()
 1498             op = canvas.rect
 1499             kwds = dict(fill=0, stroke=0)
 1500             if bc and bw:
 1501                 canvas.setStrokeColor(bc)
 1502                 canvas.setLineWidth(bw)
 1503                 kwds['stroke'] = 1
 1504                 br = getattr(style, 'borderRadius', 0)
 1505                 if br and not debug:
 1506                     op = canvas.roundRect
 1507                     kwds['radius'] = br
 1508             if bg:
 1509                 canvas.setFillColor(bg)
 1510                 kwds['fill'] = 1
 1511             bp = getattr(style, 'borderPadding', 0)
 1512             op(leftIndent - bp,
 1513                -bp,
 1514                self.width - (leftIndent + style.rightIndent) + 2 * bp,
 1515                self.height + 2 * bp,
 1516                **kwds)
 1517             canvas.restoreState()
 1518 
 1519         nLines = len(lines)
 1520         bulletText = self.bulletText
 1521         if nLines > 0:
 1522             _offsets = getattr(self, '_offsets', [0])
 1523             _offsets += (nLines - len(_offsets)) * [_offsets[-1]]
 1524             canvas.saveState()
 1525             alignment = style.alignment
 1526             offset = style.firstLineIndent + _offsets[0]
 1527             lim = nLines - 1
 1528             noJustifyLast = not (hasattr(self, '_JustifyLast') and self._JustifyLast)
 1529 
 1530             if blPara.kind == 0:
 1531                 if alignment == TA_LEFT:
 1532                     dpl = _leftDrawParaLine
 1533                 elif alignment == TA_CENTER:
 1534                     dpl = _centerDrawParaLine
 1535                 elif self.style.alignment == TA_RIGHT:
 1536                     dpl = _rightDrawParaLine
 1537                 elif self.style.alignment == TA_JUSTIFY:
 1538                     dpl = _justifyDrawParaLine
 1539                 f = blPara
 1540                 cur_y = self.height - getattr(f, 'ascent', f.fontSize)    # TODO fix XPreformatted to remove this hack
 1541                 if bulletText:
 1542                     offset = _drawBullet(canvas, offset, cur_y, bulletText, style)
 1543 
 1544                 #set up the font etc.
 1545                 canvas.setFillColor(f.textColor)
 1546 
 1547                 tx = self.beginText(cur_x, cur_y)
 1548                 if autoLeading == 'max':
 1549                     leading = max(leading, LEADING_FACTOR * f.fontSize)
 1550                 elif autoLeading == 'min':
 1551                     leading = LEADING_FACTOR * f.fontSize
 1552 
 1553                 #now the font for the rest of the paragraph
 1554                 tx.setFont(f.fontName, f.fontSize, leading)
 1555                 ws = getattr(tx, '_wordSpace', 0)  
 1556                 t_off = dpl(tx, offset, ws, lines[0][1], noJustifyLast and nLines == 1)
 1557                 if (hasattr(f, 'underline') and f.underline) or f.link or (hasattr(f, 'strike') and f.strike):
 1558                     xs = tx.XtraState = ABag()
 1559                     xs.cur_y = cur_y
 1560                     xs.f = f
 1561                     xs.style = style
 1562                     xs.lines = lines
 1563                     xs.underlines = []
 1564                     xs.underlineColor = None
 1565                     # XXX Modified for XHTML2PDF
 1566                     xs.backgrounds = []
 1567                     xs.backgroundColor = None
 1568                     xs.backgroundFontSize = None
 1569                     xs.strikes = []
 1570                     xs.strikeColor = None
 1571                     # XXX Modified for XHTML2PDF
 1572                     xs.strikeFontSize = None
 1573                     xs.links = []
 1574                     xs.link = f.link
 1575                     canvas.setStrokeColor(f.textColor)
 1576                     dx = t_off + leftIndent
 1577                     if dpl != _justifyDrawParaLine: ws = 0
 1578                     # XXX Never underline!
 1579                     underline = f.underline
 1580                     strike = f.strike
 1581                     link = f.link
 1582                     if underline:
 1583                         _do_under_line(0, dx, ws, tx)
 1584                     if strike:
 1585                         _do_under_line(0, dx, ws, tx, lm=0.125)
 1586                     if link: _do_link_line(0, dx, ws, tx)
 1587 
 1588                     #now the middle of the paragraph, aligned with the left margin which is our origin.
 1589                     for i in six.moves.range(1, nLines):
 1590                         ws = lines[i][0]
 1591                         t_off = dpl(tx, _offsets[i], ws, lines[i][1], noJustifyLast and i == lim)
 1592                         if dpl != _justifyDrawParaLine: ws = 0
 1593                         if underline: _do_under_line(i, t_off + leftIndent, ws, tx)
 1594                         if strike: _do_under_line(i, t_off + leftIndent, ws, tx, lm=0.125)
 1595                         if link: _do_link_line(i, t_off + leftIndent, ws, tx)
 1596                 else:
 1597                     for i in six.moves.range(1, nLines):
 1598                         dpl(tx, _offsets[i], lines[i][0], lines[i][1], noJustifyLast and i == lim)
 1599             else:
 1600                 f = lines[0]
 1601                 cur_y = self.height - getattr(f, 'ascent', f.fontSize)    # TODO fix XPreformatted to remove this hack
 1602                 # default?
 1603                 dpl = _leftDrawParaLineX
 1604                 if bulletText:
 1605                     offset = _drawBullet(canvas, offset, cur_y, bulletText, style)
 1606                 if alignment == TA_LEFT:
 1607                     dpl = _leftDrawParaLineX
 1608                 elif alignment == TA_CENTER:
 1609                     dpl = _centerDrawParaLineX
 1610                 elif self.style.alignment == TA_RIGHT:
 1611                     dpl = _rightDrawParaLineX
 1612                 elif self.style.alignment == TA_JUSTIFY:
 1613                     dpl = _justifyDrawParaLineX
 1614                 else:
 1615                     raise ValueError("bad align %s" % repr(alignment))
 1616 
 1617                 #set up the font etc.
 1618                 tx = self.beginText(cur_x, cur_y)
 1619                 xs = tx.XtraState = ABag()
 1620                 xs.textColor = None
 1621                 # XXX Modified for XHTML2PDF
 1622                 xs.backColor = None
 1623                 xs.rise = 0
 1624                 xs.underline = 0
 1625                 xs.underlines = []
 1626                 xs.underlineColor = None
 1627                 # XXX Modified for XHTML2PDF
 1628                 xs.background = 0
 1629                 xs.backgrounds = []
 1630                 xs.backgroundColor = None
 1631                 xs.backgroundFontSize = None
 1632                 xs.strike = 0
 1633                 xs.strikes = []
 1634                 xs.strikeColor = None
 1635                 # XXX Modified for XHTML2PDF
 1636                 xs.strikeFontSize = None
 1637                 xs.links = []
 1638                 xs.link = None
 1639                 xs.leading = style.leading
 1640                 xs.leftIndent = leftIndent
 1641                 tx._leading = None
 1642                 tx._olb = None
 1643                 xs.cur_y = cur_y
 1644                 xs.f = f
 1645                 xs.style = style
 1646                 xs.autoLeading = autoLeading
 1647 
 1648                 tx._fontname, tx._fontsize = None, None
 1649                 dpl(tx, offset, lines[0], noJustifyLast and nLines == 1)
 1650                 _do_post_text(tx)
 1651 
 1652                 #now the middle of the paragraph, aligned with the left margin which is our origin.
 1653                 for i in six.moves.range(1, nLines):
 1654                     f = lines[i]
 1655                     dpl(tx, _offsets[i], f, noJustifyLast and i == lim)
 1656                     _do_post_text(tx)
 1657 
 1658             canvas.drawText(tx)
 1659             canvas.restoreState()
 1660 
 1661     def getPlainText(self, identify=None):
 1662         """
 1663         Convenience function for templates which want access
 1664         to the raw text, without XML tags.
 1665         """
 1666 
 1667         frags = getattr(self, 'frags', None)
 1668         if frags:
 1669             plains = []
 1670             for frag in frags:
 1671                 if hasattr(frag, 'text'):
 1672                     plains.append(frag.text)
 1673             return ''.join(plains)
 1674         elif identify:
 1675             text = getattr(self, 'text', None)
 1676             if text is None: text = repr(self)
 1677             return text
 1678         else:
 1679             return ''
 1680 
 1681     def getActualLineWidths0(self):
 1682         """
 1683         Convenience function; tells you how wide each line
 1684         actually is.  For justified styles, this will be
 1685         the same as the wrap width; for others it might be
 1686         useful for seeing if paragraphs will fit in spaces.
 1687         """
 1688 
 1689         assert hasattr(self, 'width'), "Cannot call this method before wrap()"
 1690         if self.blPara.kind:
 1691             func = lambda frag, w=self.width: w - frag.extraSpace
 1692         else:
 1693             func = lambda frag, w=self.width: w - frag[0]
 1694         return map(func, self.blPara.lines)
 1695 
 1696 
 1697 if __name__ == '__main__':    # NORUNTESTS
 1698     def dumpParagraphLines(P):
 1699         print ('dumpParagraphLines(<Paragraph @ %d>)') % id(P)
 1700         lines = P.blPara.lines
 1701         for l, line in enumerate(lines):
 1702             line = lines[l]
 1703             if hasattr(line, 'words'):
 1704                 words = line.words
 1705             else:
 1706                 words = line[1]
 1707             nwords = len(words)
 1708             print ('line%d: %d(%s)\n  ') % (l, nwords, str(getattr(line, 'wordCount', 'Unknown'))),
 1709             for w in six.moves.range(nwords):
 1710                 print ("%d:'%s'") % (w, getattr(words[w], 'text', words[w])),
 1711             print()
 1712 
 1713     def fragDump(w):
 1714         R = ["'%s'" % w[1]]
 1715         for a in ('fontName', 'fontSize', 'textColor', 'rise', 'underline', 'strike', 'link', 'cbDefn', 'lineBreak'):
 1716             if hasattr(w[0], a):
 1717                 R.append('%s=%r' % (a, getattr(w[0], a)))
 1718         return ', '.join(R)
 1719 
 1720     def dumpParagraphFrags(P):
 1721         print ('dumpParagraphFrags(<Paragraph @ %d>) minWidth() = %.2f') % (id(P), P.minWidth())
 1722         frags = P.frags
 1723         n = len(frags)
 1724         for l in six.moves.range(n):
 1725             print ("frag%d: '%s' %s") % (
 1726             l, frags[l].text, ' '.join(['%s=%s' % (k, getattr(frags[l], k)) for k in frags[l].__dict__ if k != text]))
 1727 
 1728         l = 0
 1729         cum = 0
 1730         for W in _getFragWords(frags):
 1731             cum += W[0]
 1732             print ("fragword%d: cum=%3d size=%d") % (l, cum, W[0]),
 1733             for w in W[1:]:
 1734                 print ('(%s)') % fragDump(w),
 1735             print()
 1736             l += 1
 1737 
 1738     from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
 1739     from reportlab.lib.units import cm
 1740 
 1741     TESTS = sys.argv[1:]
 1742     if TESTS == []:
 1743         TESTS = ['4']
 1744 
 1745     def flagged(i, TESTS=TESTS):
 1746         return 'all' in TESTS or '*' in TESTS or str(i) in TESTS
 1747 
 1748     styleSheet = getSampleStyleSheet()
 1749     B = styleSheet['BodyText']
 1750     style = ParagraphStyle("discussiontext", parent=B)
 1751     style.fontName = 'Helvetica'
 1752     if flagged(1):
 1753         text = '''The <font name=courier color=green>CMYK</font> or subtractive method follows the way a printer
 1754 mixes three pigments (cyan, magenta, and yellow) to form colors.
 1755 Because mixing chemicals is more difficult than combining light there
 1756 is a fourth parameter for darkness.  For example a chemical
 1757 combination of the <font name=courier color=green>CMY</font> pigments generally never makes a perfect
 1758 black -- instead producing a muddy color -- so, to get black printers
 1759 don't use the <font name=courier color=green>CMY</font> pigments but use a direct black ink.  Because
 1760 <font name=courier color=green>CMYK</font> maps more directly to the way printer hardware works it may
 1761 be the case that &amp;| &amp; | colors specified in <font name=courier color=green>CMYK</font> will provide better fidelity
 1762 and better control when printed.
 1763 '''
 1764         P = Paragraph(text, style)
 1765         dumpParagraphFrags(P)
 1766         aW, aH = 456.0, 42.8
 1767         w, h = P.wrap(aW, aH)
 1768         dumpParagraphLines(P)
 1769         S = P.split(aW, aH)
 1770         for s in S:
 1771             s.wrap(aW, aH)
 1772             dumpParagraphLines(s)
 1773             aH = 500
 1774 
 1775     if flagged(2):
 1776         P = Paragraph("""Price<super><font color="red">*</font></super>""", styleSheet['Normal'])
 1777         dumpParagraphFrags(P)
 1778         w, h = P.wrap(24, 200)
 1779         dumpParagraphLines(P)
 1780 
 1781     if flagged(3):
 1782         text = """Dieses Kapitel bietet eine schnelle <b><font color=red>Programme :: starten</font></b>
 1783 <onDraw name=myIndex label="Programme :: starten">
 1784 <b><font color=red>Eingabeaufforderung :: (&gt;&gt;&gt;)</font></b>
 1785 <onDraw name=myIndex label="Eingabeaufforderung :: (&gt;&gt;&gt;)">
 1786 <b><font color=red>&gt;&gt;&gt; (Eingabeaufforderung)</font></b>
 1787 <onDraw name=myIndex label="&gt;&gt;&gt; (Eingabeaufforderung)">
 1788 Einf&#xfc;hrung in Python <b><font color=red>Python :: Einf&#xfc;hrung</font></b>
 1789 <onDraw name=myIndex label="Python :: Einf&#xfc;hrung">.
 1790 Das Ziel ist, die grundlegenden Eigenschaften von Python darzustellen, ohne
 1791 sich zu sehr in speziellen Regeln oder Details zu verstricken. Dazu behandelt
 1792 dieses Kapitel kurz die wesentlichen Konzepte wie Variablen, Ausdr&#xfc;cke,
 1793 Kontrollfluss, Funktionen sowie Ein- und Ausgabe. Es erhebt nicht den Anspruch,
 1794 umfassend zu sein."""
 1795         P = Paragraph(text, styleSheet['Code'])
 1796         dumpParagraphFrags(P)
 1797         w, h = P.wrap(6 * 72, 9.7 * 72)
 1798         dumpParagraphLines(P)
 1799 
 1800     if flagged(4):
 1801         text = '''Die eingebaute Funktion <font name=Courier>range(i, j [, stride])</font><onDraw name=myIndex label="eingebaute Funktionen::range()"><onDraw name=myIndex label="range() (Funktion)"><onDraw name=myIndex label="Funktionen::range()"> erzeugt eine Liste von Ganzzahlen und f&#xfc;llt sie mit Werten <font name=Courier>k</font>, f&#xfc;r die gilt: <font name=Courier>i &lt;= k &lt; j</font>. Man kann auch eine optionale Schrittweite angeben. Die eingebaute Funktion <font name=Courier>xrange()</font><onDraw name=myIndex label="eingebaute Funktionen::xrange()"><onDraw name=myIndex label="xrange() (Funktion)"><onDraw name=myIndex label="Funktionen::xrange()"> erf&#xfc;llt einen &#xe4;hnlichen Zweck, gibt aber eine unver&#xe4;nderliche Sequenz vom Typ <font name=Courier>XRangeType</font><onDraw name=myIndex label="XRangeType"> zur&#xfc;ck. Anstatt alle Werte in der Liste abzuspeichern, berechnet diese Liste ihre Werte, wann immer sie angefordert werden. Das ist sehr viel speicherschonender, wenn mit sehr langen Listen von Ganzzahlen gearbeitet wird. <font name=Courier>XRangeType</font> kennt eine einzige Methode, <font name=Courier>s.tolist()</font><onDraw name=myIndex label="XRangeType::tolist() (Methode)"><onDraw name=myIndex label="s.tolist() (Methode)"><onDraw name=myIndex label="Methoden::s.tolist()">, die seine Werte in eine Liste umwandelt.'''
 1802         aW = 420
 1803         aH = 64.4
 1804         P = Paragraph(text, B)
 1805         dumpParagraphFrags(P)
 1806         w, h = P.wrap(aW, aH)
 1807         print ('After initial wrap', w, h)
 1808         dumpParagraphLines(P)
 1809         S = P.split(aW, aH)
 1810         dumpParagraphFrags(S[0])
 1811         w0, h0 = S[0].wrap(aW, aH)
 1812         print ('After split wrap', w0, h0)
 1813         dumpParagraphLines(S[0])
 1814 
 1815     if flagged(5):
 1816         text = '<para> %s <![CDATA[</font></b>& %s < >]]></para>' % (chr(163), chr(163))
 1817         P = Paragraph(text, styleSheet['Code'])
 1818         dumpParagraphFrags(P)
 1819         w, h = P.wrap(6 * 72, 9.7 * 72)
 1820         dumpParagraphLines(P)
 1821 
 1822     if flagged(6):
 1823         for text in [
 1824             '''Here comes <FONT FACE="Helvetica" SIZE="14pt">Helvetica 14</FONT> with <STRONG>strong</STRONG> <EM>emphasis</EM>.''',
 1825             '''Here comes <font face="Helvetica" size="14pt">Helvetica 14</font> with <Strong>strong</Strong> <em>emphasis</em>.''',
 1826             '''Here comes <font face="Courier" size="3cm">Courier 3cm</font> and normal again.''',
 1827         ]:
 1828             P = Paragraph(text, styleSheet['Normal'], caseSensitive=0)
 1829             dumpParagraphFrags(P)
 1830             w, h = P.wrap(6 * 72, 9.7 * 72)
 1831             dumpParagraphLines(P)
 1832 
 1833     if flagged(7):
 1834         text = """<para align="CENTER" fontSize="24" leading="30"><b>Generated by:</b>Dilbert</para>"""
 1835         P = Paragraph(text, styleSheet['Code'])
 1836         dumpParagraphFrags(P)
 1837         w, h = P.wrap(6 * 72, 9.7 * 72)
 1838         dumpParagraphLines(P)
 1839 
 1840     if flagged(8):
 1841         text = """- bullet 0<br/>- bullet 1<br/>- bullet 2<br/>- bullet 3<br/>- bullet 4<br/>- bullet 5"""
 1842         P = Paragraph(text, styleSheet['Normal'])
 1843         dumpParagraphFrags(P)
 1844         w, h = P.wrap(6 * 72, 9.7 * 72)
 1845         dumpParagraphLines(P)
 1846         S = P.split(6 * 72, h / 2.0)
 1847         print (len(S))
 1848         dumpParagraphLines(S[0])
 1849         dumpParagraphLines(S[1])
 1850 
 1851     if flagged(9):
 1852         text = """Furthermore, the fundamental error of
 1853 regarding <img src="../docs/images/testimg.gif" width="3" height="7"/> functional notions as
 1854 categorial delimits a general
 1855 convention regarding the forms of the<br/>
 1856 grammar. I suggested that these results
 1857 would follow from the assumption that"""
 1858         P = Paragraph(text, ParagraphStyle('aaa', parent=styleSheet['Normal'], align=TA_JUSTIFY))
 1859         dumpParagraphFrags(P)
 1860         w, h = P.wrap(6 * cm - 12, 9.7 * 72)
 1861         dumpParagraphLines(P)
 1862 
 1863     if flagged(10):
 1864         text = """a b c\xc2\xa0d e f"""
 1865         P = Paragraph(text, ParagraphStyle('aaa', parent=styleSheet['Normal'], align=TA_JUSTIFY))
 1866         dumpParagraphFrags(P)
 1867         w, h = P.wrap(6 * cm - 12, 9.7 * 72)
 1868         dumpParagraphLines(P)