File: NewParagraph.py

package info (click to toggle)
python-wordaxe 0.3.2-1
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 1,228 kB
  • ctags: 786
  • sloc: python: 9,814; makefile: 5
file content (1255 lines) | stat: -rw-r--r-- 50,762 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# A new paragraph implementation

__doc__ = """
A new Paragraph implementation.

A Paragraph can be constructed in one of two ways:
 * supplying text (with support for a HTML-subset formatting)
 * supplying frags directly.
If text is supplied, the constructor calls the ParaParser
to parse it and construct frags.

Please note that a "frag" is different from the ReportLab
standard "frag": Here, a frag is either a StyledWord instance
or a StyledFragment instance (see para_fragments.py).
However, there are two functions in para_fragments.py that
allow you to convert classic RL frag lists to wordaxe frag lists
and vice versa:
frags_reportlab_to_wordaxe and frags_wordaxe_to_reportlab

The following is a definition of some typographic concepts:
´

BASELINE, ASCENT, DESCENT:
  Characters seem to "rest" on the baseline.
  The ASCENT of a font is the maximum distance from the baseline to
  the top of upper-case characters (accents not counted).
  Usually,all upper-case characters in a fonts have the same height,
  and characters like 'b', 'd', 'l', 't' have this same height, too.
  The DESCENT of a font is the maximum distance from the baseline
  to the bottom of characters like 'f','g', 'j' etc.

  Note that other, non-character glyphs (like the integral symbol)
  may differ in height, for example, the integral symbol's height
  is greater than the font's ascent+descent.

LEADING:
  (pronounced like heading, it comes from the metal used in
  typesetting).
-----------------------------------------------------------------
  Note: The definition used inside the ReportLab toolkit is
  different from the definition used elsewhere!
-----------------------------------------------------------------
  While the common definition is "the space between the bottom
  of the characters of one line and the top of the characters in
  the next line (i.e. line height = ASCENT+DESCENT+LEADING),
  ReportLab uses a different definition - see userguide.pdf,
  "Text object methods, Interline spacing (Leading)".

See also:
 * http://developer.apple.com/documentation/mac/Text/Text-186.html
 * http://java.sun.com/developer/onlineTraining/Media/2DText/other.html
Ascent:


"""

from reportlab.lib.units import cm
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus.flowables import Flowable
from reportlab.rl_config import platypus_link_underline
import re
from copy import copy, deepcopy

import wordaxe
from wordaxe.hyphen import HyphenationPoint, SHY, HyphenatedWord, Hyphenator
from wordaxe.rl.paraparser import ParaParser, NoBrParaParser
from wordaxe.rl.para_fragments import *

pt = 1 # Points is the base unit in RL

# This is more or less copied from RL paragraph

def cleanBlockQuotedText(text,joiner=u' '):
    """This is an internal utility which takes triple-
    quoted text form within the document and returns
    (hopefully) the paragraph the user intended originally."""
    def _lineClean(line):
        return u' '.join([x for x in line.strip().split(u' ') if x])
    lines=[_lineClean(line) for line in text.split('\n')]
    return joiner.join(line for line in lines if line)


def setXPos(tx,dx):
    if dx>1e-6 or dx<-1e-6:
        tx.setXPos(dx)


# This is more or less copied from RL paragraph

def imgVRange(h,va,fontSize):
    '''return bottom,top offsets relative to baseline(0)'''
    if va=='baseline':
        iyo = 0
    elif va in ('text-top','top'):
        iyo = fontSize-h
    elif va=='middle':
        iyo = fontSize - (1.2*fontSize+h)*0.5
    elif va in ('text-bottom','bottom'):
        iyo = fontSize - 1.2*fontSize
    elif va=='super':
        iyo = 0.5*fontSize
    elif va=='sub':
        iyo = -0.5*fontSize
    elif hasattr(va,'normalizedValue'):
        iyo = va.normalizedValue(fontSize)
    else:
        iyo = va
    return iyo,iyo+h

_56=5./6
_16=1./6
def _putFragLine(cur_x, tx, line):
    #print "_putFragLine", line
    assert isinstance(line, Line)
    xs = tx.XtraState
    cur_y = xs.cur_y
    #print "_putFragLine: xs.cur_y:", xs.cur_y
    x0 = tx._x0
    autoLeading = xs.autoLeading
    leading = xs.leading
    cur_x += xs.leftIndent
    dal = autoLeading in ('min','max')
    if dal:
        if autoLeading=='max':
            ascent = max(_56*leading,line.ascent)
            descent = max(_16*leading,-line.descent)
        else:
            ascent = line.ascent
            descent = -line.descent
        leading = ascent+descent
    if tx._leading!=leading:
        tx.setLeading(leading)
    if dal:
        olb = tx._olb
        if olb is not None:
            xcy = olb-ascent
            if tx._oleading!=leading:
                cur_y += leading - tx._oleading
            if abs(xcy-cur_y)>1e-8:
                cur_y = xcy
                tx.setTextOrigin(x0,cur_y)
                xs.cur_y = cur_y
        tx._olb = cur_y - descent
        tx._oleading = leading
    ws = getattr(tx,'_wordSpace',0)
    nSpaces = 0

    fragments = list(frags_wordaxe_to_reportlab(line.iter_print_frags()))
    for frag in fragments:
        #print "render %r" % getattr(frag, "text", "--")
        f = frag.style
        if hasattr(f,'cbDefn'):
            #print "render", f
            cbDefn = f.cbDefn
            kind = cbDefn.kind
            if kind=='img':
                #draw image cbDefn,cur_y,cur_x
                w = cbDefn.width
                h = cbDefn.height
                txfs = tx._fontsize
                if txfs is None:
                    txfs = xs.style.fontSize
                iy0,iy1 = imgVRange(h,cbDefn.valign,txfs)
                cur_x_s = cur_x + nSpaces*ws
                tx._canvas.drawImage(cbDefn.image,cur_x_s,cur_y+iy0,w,h,mask='auto')
                cur_x += w
                cur_x_s += w
                setXPos(tx,cur_x_s-tx._x0)
            else:
                name = cbDefn.name
                if kind=='anchor':
                    tx._canvas.bookmarkHorizontal(name,cur_x,cur_y+leading)
                else:
                    func = getattr(tx._canvas,name,None)
                    if not func:
                        raise AttributeError("Missing %s callback attribute '%s'" % (kind,name))
                    func(tx._canvas,kind,cbDefn.label)
            if frag is fragments[-1]:
                if not tx._fontname:
                    tx.setFont(xs.style.fontName,xs.style.fontSize)
                    tx._textOut('',1)
                elif kind=='img':
                    tx._textOut('',1)
        else:
            cur_x_s = cur_x + nSpaces*ws
            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)
            text = frag.text
            tx._textOut(text,frag is fragments[-1])    # cheap textOut

            # Background colors (done like underline)
            #print "f:", repr(f)
            backColor = getattr(f, "backColor", None)
            if xs.backgroundColor != backColor or xs.backgroundFontSize != f.fontSize:
                if xs.backgroundColor is not None:
                    xs.backgrounds.append( (xs.background_x, cur_x_s, xs.backgroundColor, xs.backgroundFontSize) )
                xs.background_x = cur_x_s
                xs.backgroundColor = backColor
                xs.backgroundFontSize = f.fontSize
            # Underline
            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, cur_x_s, xs.underlineColor) )
                    xs.underlineColor = None
                elif xs.textColor!=xs.underlineColor:
                    xs.underlines.append( (xs.underline_x, cur_x_s, 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, cur_x_s, xs.strikeColor) )
                    xs.strikeColor = None
                elif xs.textColor!=xs.strikeColor:
                    xs.strikes.append( (xs.strike_x, cur_x_s, 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, cur_x_s, 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, cur_x_s, xs.link, xs.linkColor) )
                    xs.link = f.link
                    xs.link_x = cur_x_s
                    xs.linkColor = xs.textColor
            txtlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize)
            # TODO why is this? We already have got the text length!?
            cur_x += txtlen
            nSpaces += text.count(' ')
    cur_x_s = cur_x+(nSpaces-1)*ws
    if xs.underline:
        xs.underlines.append( (xs.underline_x, cur_x_s, xs.underlineColor) )
    if xs.backgroundColor is not None:
        xs.backgrounds.append( (xs.background_x, cur_x_s, xs.backgroundColor, xs.backgroundFontSize) )
    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 tx._x0!=x0:
        setXPos(tx,x0-tx._x0)

def _drawBullet(canvas, offset, cur_y, bulletText, style):
    '''draw a bullet text could be a simple string or a frag list'''
    tx2 = canvas.beginText(style.bulletIndent, cur_y+getattr(style,"bulletOffsetY",0))
    tx2.setFont(style.bulletFontName, style.bulletFontSize)
    tx2.setFillColor(hasattr(style,'bulletColor') and style.bulletColor or style.textColor)
    if isinstance(bulletText,basestring):
        tx2.textOut(bulletText)
    else:
        for f in bulletText:
            tx2.setFont(f.fontName, f.fontSize)
            tx2.setFillColor(f.textColor)
            tx2.textOut(f.text)

    canvas.drawText(tx2)
    #AR making definition lists a bit less ugly
    #bulletEnd = tx2.getX()
    bulletEnd = tx2.getX() + style.bulletFontSize * 0.6
    offset = max(offset,bulletEnd - style.leftIndent)
    return offset

def _handleBulletWidth(bulletText, style, max_widths):
    '''work out bullet width and adjust max_widths[0] if neccessary
    '''
    if bulletText:
        if isinstance(bulletText,basestring):
            bulletWidth = pdfmetrics.stringWidth( bulletText, style.bulletFontName, style.bulletFontSize)
        else:
            #it's a list of fragments
            bulletWidth = 0
            for f in bulletText:
                bulletWidth = bulletWidth + pdfmetrics.stringWidth(f.text, f.fontName, f.fontSize)
        bulletRight = style.bulletIndent + bulletWidth + 0.6 * style.bulletFontSize
        indent = style.leftIndent+style.firstLineIndent
        if bulletRight > indent:
            #..then it overruns, and we have less space available on line 1
            max_widths[0] -= (bulletRight - indent)

_scheme_re = re.compile('^[a-zA-Z][-+a-zA-Z0-9]+$')
def _doLink(tx,link,rect):
    if isinstance(link,unicode):
        link = link.encode('utf8')
    parts = link.split(':',1)
    scheme = len(parts)==2 and parts[0].lower() or ''
    if _scheme_re.match(scheme) and scheme!='document':
        kind=scheme.lower()=='pdf' and 'GoToR' or 'URI'
        if kind=='GoToR': link = parts[1]
        tx._canvas.linkURL(link, rect, relative=1, kind=kind)
    else:
        if link[0]=='#':
            link = link[1:]
            scheme=''
        tx._canvas.linkRect("", scheme!='document' and link or parts[1], rect, relative=1)

def _do_post_text(tx):

    xs = tx.XtraState
    leading = xs.style.leading
    autoLeading = xs.autoLeading

    if True:
        f = xs.f
        if autoLeading=='max':
            leading = max(leading,1.2*f.fontSize)
        elif autoLeading=='min':
            leading = 1.2*f.fontSize
        ff = 0.125*f.fontSize
        y0 = xs.cur_y
        y = y0 - ff

        # Background
        ulc = None
        for x1,x2,c,fs in xs.backgrounds:
            # print "u",x1,x2,c, leading, ff, i, fs
            if c!=ulc:
                tx._canvas.setFillColor(c)
                ulc = c
            #tx._canvas.rect(x1, y, x2-x1, fs, fill=1, stroke=0)
            tx._canvas.rect(x1, y - ff, x2-x1, fs, fill=1, stroke=0)
        xs.backgrounds = []
        xs.background = 0
        xs.backgroundColor = None
        xs.backgroundFontSize = None

        # Underline
        csc = None
        for x1,x2,c in xs.underlines:
            if c!=csc:
                tx._canvas.setStrokeColor(c)
                csc = c
            tx._canvas.line(x1, y, x2, y)
        xs.underlines = []
        xs.underline=0
        xs.underlineColor=None

        ys = y0 + 2*ff
        for x1,x2,c in xs.strikes:
            if c!=csc:
                tx._canvas.setStrokeColor(c)
                csc = c
            tx._canvas.line(x1, ys, x2, ys)
        xs.strikes = []
        xs.strike=0
        xs.strikeColor=None

        yl = y + leading
        for x1,x2,link,c in xs.links:
            if platypus_link_underline:
                if c!=csc:
                    tx._canvas.setStrokeColor(c)
                    csc = c
                tx._canvas.line(x1, y, x2, y)
            _doLink(tx, link, (x1, y, x2, yl))
    xs.links = []
    xs.link=None
    xs.linkColor=None
    #print "leading:", leading
    xs.cur_y -= leading
    #print "xs.cur_y:", xs.cur_y


def textTransformFrags(frags,style):
    tt = style.textTransform
    if tt:
        tt=tt.lower()
        if tt=='lowercase':
            tt = unicode.lower
        elif tt=='uppercase':
            tt = unicode.upper
        elif  tt=='capitalize':
            tt = unicode.title
        elif tt=='none':
            return
        else:
            raise ValueError('ParaStyle.textTransform value %r is invalid' % style.textTransform)
        n = len(frags)
        if n==1:
            #single fragment the easy case
            frags[0].text = tt(frags[0].text)
        elif tt is unicode.title:
            pb = True
            for f in frags:
                t = f.text
                if not t: continue
                u = t
                if u.startswith(u' ') or pb:
                    u = tt(u)
                else:
                    i = u.find(u' ')
                    if i>=0:
                        u = u[:i]+tt(u[i:])
                pb = u.endswith(u' ')
                f.text = u
        else:
            for f in frags:
                t = f.text
                if not t: continue
                f.text = tt(t)


# Here follows a clean(er) paragraph implemention

class Paragraph(Flowable):
    "A simple new implementation for Paragraph flowables."

    def __init__(self, text, style, bulletText = None, frags=None, lines=None, caseSensitive=1, encoding='utf-8', keepWhiteSpace=False, textCleaner=cleanBlockQuotedText):
        """
        Either text and style or frags must be supplied.
        """
        self.caseSensitive = caseSensitive
        self.style = style
        self.bulletText = bulletText
        self.keepWhiteSpace = keepWhiteSpace # TODO: Unterstützen
        self._cache = {}

        if text is None:
            assert frags is not None or lines is not None
            self.frags = frags
            if frags is None:
                #print id(self), "init with %d lines" % len(lines)
                for line in lines: assert isinstance(line, Line)
                self._cache['lines'] = lines
                self._cache['height'] = sum([line.height for line in lines])
                self._cache['avail'] = True
            else:
                #print id(self), "init with frags", frags
                for frag in frags: assert isinstance(frag, Fragment)
                self.frags = frags
        else:
            #print id(self), "init with text"
            assert isinstance(text, basestring)
            # parse text
            if not isinstance(text, unicode):
                text = unicode(text, encoding)
            if textCleaner: text = textCleaner(text)
            self.frags = list(self.parse(text, style, bulletText))
        self.text = text

    def parse(self, text, style, bulletText):
        """
        Use the NoBrParaParser to create a list of words.
        Yields StyledWords, StyledSpace and other entries,
        but StyledTexts are grouped to StyledWords.
        """
        wordFrags = []
        "Use the NoBrParaParser to create a sequence of fragments"
        parser = NoBrParaParser()
        parser.caseSensitive = self.caseSensitive
        style, frag_list, bullet_frag_list = parser.parse(text, style)
        if bullet_frag_list:
            self.bulletText = bullet_frag_list
        textTransformFrags(frag_list, style)
        self.style = style
        return frags_reportlab_to_wordaxe(frag_list, style)

    def __repr__(self):
        if self.frags:
            return "%s(frags=%r)" % (self.__class__.__name__, self.frags)
        elif 'lines' in self._cache:
            return "%s(_lines=%r)" % (self.__class__.__name__, self._cache['lines'])


    def calcLineHeight(self, line):
        """
        Compute the height needed for a given line.
        """
        #print "calcLineHeight", self.style.leading
        return self.style.leading
        # TODO or should this be computed from the frags?

    def wrap(self, availW, availH):
        """
        Return the actually used size.
        """
        #print id(self), "wrap", availW, availH
        avail = self._cache.get('avail')
        if (avail is True # paragraph has no frags, only lines
                or avail == (availW, availH)): # already wrapped to this size
            return availW, self._cache['height']
        else: # needs to be wrapped
            style = self.style
            leftIndent = style.leftIndent
            first_line_width = availW - (leftIndent+style.firstLineIndent) - style.rightIndent
            later_widths = availW - leftIndent - style.rightIndent
            max_widths = [first_line_width, later_widths]
            return self.i_wrap(availW, availH, max_widths)

    def i_wrap(self, availW, availH, max_widths):
        """
        Return the height and width that are actually needed.
        Note:
        This will abort if the text does not fit entirely.
        The lines measured so far will be stored in a private
        attribute _cache['lines'] (to improve performance).
        TODO: Should StyledSpaces be ignored before or after StyledNewLines?
        """
        #print id(self), "i_wrap", availW, availH
        lines = []          # lines so far
        sumHeight = 0       # sum of lines heights so far
        lineHeight = 0      # height of current line
        width = 0           # width of current line
        lineFrags = []      # (flattened) fragments in current line

        _handleBulletWidth(self.bulletText, self.style, max_widths)

        def iter_widths(max_widths=max_widths):
            # an iterator that repeats the last element infinitely
            for w in max_widths: yield w
            while True: yield w
        width_iter = iter_widths()
        max_width = width_iter.next()

        frags_remaining = self.frags[:]
        while frags_remaining:
            if sumHeight > availH:
                #print "sumHeight > availH, break, lineFrags=%s" % lineFrags
                break
            frag = frags_remaining.pop(0)
            actions = [("ERROR",None)]
            w = 0
            if isinstance(frag, StyledNewLine):
                actions = [("ADD",frag), ("LINEFEED",None)]
            elif hasattr(frag, "width"):
                w = frag.width
                if width + w > max_width:
                    # does not fit
                    #print "does not fit:", frag, width, w, max_width
                    if isinstance(frag, StyledWord):
                        # Hyphenation support
                        act, left, right, spaceWasted \
                            = self.findBestSolution(lineFrags, frag, max_width-width, True)
                        # TODO: for now, always try squeeze
                        if act == self.OVERFLOW:
                            actions = [("LINEFEED",None),("PUSH",frag)]
                        elif act == self.SQUEEZE:
                            actions = [("ADD",frag),("LINEFEED",None)]
                        elif act == self.HYPHENATE:
                            setattr(left,"_source", frag)
                            setattr(right,"_source", frag)
                            actions = [("ADD",left),("LINEFEED",None),("PUSH",right)]
                        else:
                            raise AssertionError
                    else:
                        actions = [("LINEFEED",None),("PUSH",frag)]
                else:
                    # will fit into current line
                    actions = [("ADD",frag)]
            else:
                # Some Meta Fragment
                action = ("ADD",frag)
            for (act,afrag) in actions:
                #print act, width
                if act == "LINEFEED":
                    if not self.keepWhiteSpace:
                        # ignore space at the end of the line for the
                        # width calculation
                        for f in reversed(lineFrags):
                            if isinstance(f, StyledSpace) or not f.width:
                                width -= f.width
                                if width <= 0:
                                    width = 0
                            else:
                                break
                    #print act,
                    lineHeight = self.style.leading # TODO correct height calculation
                    #print lineHeight,
                    baseline = 0   # TODO correct baseline calculation
                    line = Line(lineFrags, width, lineHeight, baseline, max_width - width, self.keepWhiteSpace)
                    lines.append(line)
                    lineFrags = []
                    width = 0
                    max_width = width_iter.next()
                    sumHeight += lineHeight
                    #print sumHeight
                elif act == "IGNORE":
                    pass
                elif act == "ADD":
                    lineFrags.append(afrag)
                    # ignore space at the start of the line for the
                    # width calculation
                    if not self.keepWhiteSpace \
                    and not width and isinstance(afrag, StyledSpace):
                            pass
                    else:
                        width += getattr(afrag, "width", 0)
                elif act == "PUSH":
                    frags_remaining.insert(0, afrag)
                else:
                    raise AssertionError("Action:%r Frag:" % (act,afrag))
        else:
            # Everything did fit
            lineHeight = self.calcLineHeight(lineFrags)
            # TODO: Why here calcLineHeight(), instead of self.style.leading as above?
            baseline = 0   # TODO correct baseline calculation
            if not lineFrags and lines:
                pass # Ignore the final line if it's empty and there are already lines.
            else:
                # ignore space at the end of the line for the
                # width calculation
                if not self.keepWhiteSpace:
                    for f in reversed(lineFrags):
                        if isinstance(f, StyledSpace) or not f.width:
                            width -= f.width
                        else:
                            break
                line = Line(lineFrags, width, lineHeight, baseline, max_width - width, self.keepWhiteSpace)
                lines.append(line)
                lineFrags = []
                width = 0
                sumHeight += lineHeight

        self.width = availW
        self.height = sumHeight
        if sumHeight > availH:
            #print id(self), "needs splitting"
            #print "lines[-1]:", lines[-1]
            #print "frags_remaining:", frags_remaining
            # don't store the last line (it does not fit)
            # TODO perhaps we have to insert a Linefeed here?
            #                        v
            assert not lineFrags, lineFrags
            assert lines
            unused = lines.pop().fragments
            if frags_remaining:
                next = frags_remaining[0]
                src = getattr(next, "_source", None)
                if src is not None:
                    # next is the right part of a hyphenation
                    left = unused[-1]
                    assert getattr(left,"_source") == src
                    unused[-1] = src
                    frags_remaining.pop(0)
                unused += frags_remaining
            assert len(unused) == len(set(unused))
            self.height -= lineHeight
            #print "%d lines, lineHeight=%f" % (len(lines), lineHeight)
            #print "in wrap: self.height=%f" % (self.height)
            #print "self.frags=%s" % self.frags
            #if len(lines)==2:
            #    print lines
        else:
            #print id(self), "fits"
            unused = []
        assert self.height <= availH, (id(self), self.height, availH)
        self._cache['lines'] = lines
        self._cache['unused'] = unused
        self._cache['avail'] = (availW, availH)
        self._cache['height'] = sumHeight
        #print "i_wrap returns", availW, sumHeight
        return availW, sumHeight

    def split(self, availWidth, availHeight):
        """
        Split the paragraph into two
        """
        #print id(self), "split", availWidth, availHeight

        avail = self._cache.get('avail')
        if avail is None or avail is not True and (
                availWidth < avail[0] or availHeight < avail[1]):
            # This can only happen when split has been called
            # without a previous wrap for this Paragraph.
            # From looking at doctemplate.py and frames.py,
            # I assume this is only the case if the free space
            # in the frame is not even enough for getSpaceBefore.
            # Thus we can safely return []

            #print "split without previous wrap"
            return []

        lines = self._cache['lines']
        #print "lines:", lines
        unused = self._cache['unused']
        #print "unused:", unused
        if len(lines) < 1: # minimum widow rows
            #print "split with lines == []"
            # Put everything on the next frame
            assert self.frags is not None
            del self._cache['avail']
            return []
        elif not unused:
            # Everything fits on this page
            #print "everything fits."
            return [self]
        else:
            style = self.style
            # height/leading computation
            autoLeading = getattr(self,'autoLeading',getattr(style,'autoLeading',''))
            leading = style.leading
            if autoLeading not in ('','off'):
                s = height = 0
                if autoLeading=='max':
                    for i,l in enumerate(lines):
                        h = max(l.ascent-l.descent,leading)
                        n = height+h
                        if n>availHeight+1e-8:
                            break
                        height = n
                        s = i+1
                elif autoLeading=='min':
                    for i,l in enumerate(lines):
                        n = height+l.ascent-l.descent
                        if n>availHeight+1e-8:
                            break
                        height = n
                        s = i+1
                else:
                    raise ValueError('invalid autoLeading value %r' % autoLeading)
            else:
                l = leading
                if autoLeading=='max':
                    l = max(leading,1.2*style.fontSize)
                elif autoLeading=='min':
                    l = 1.2*style.fontSize
                s = int(availHeight/l)
                height = s*l

            if False:
                # Widows/orphans control
                # TODO: this cannot work, since we have not yet computed
                # the lines for the second part.
                n = len(lines)
                allowWidows = getattr(self,'allowWidows',getattr(self,'allowWidows',1))
                allowOrphans = getattr(self,'allowOrphans',getattr(self,'allowOrphans',0))
                if not allowOrphans:
                    if s<=1:    #orphan?
                        del self._cache['avail']
                        return []
                if n<=s:
                    return [self]
                if not allowWidows:
                    if n==s+1: #widow?
                        if (allowOrphans and n==3) or n>3:
                            s -= 1  #give the widow some company
                        else:
                            #no room for adjustment; force the whole para onwards
                            del self._cache['avail']
                            return []
            first = self.__class__(text=None, style=self.style, bulletText=self.bulletText, lines=lines, caseSensitive=self.caseSensitive)
            first.width = self.width # TODO 20080911
            first.height = self.height
            first._JustifyLast = 1
            if style.firstLineIndent != 0:
                style = deepcopy(style)
                style.firstLineIndent = 0
            second = self.__class__(text=None, style=self.style, bulletText=None, frags=unused, caseSensitive=self.caseSensitive)
            #print "first id=%d height=%f" % (id(first), first.height)
            #print "secnd id=%d" % id(second)
            return [first, second]


    def beginText(self, x, y):
        return self.canv.beginText(x, y)

    def draw(self, debug=0):
        """
        Draw the paragraph.
        """
        #print id(self), "draw"

        # Code more or less copied from RL

        """Draws a paragraph according to the given style.
        Returns the final y position at the bottom. Not safe for
        paragraphs without spaces e.g. Japanese; wrapping
        algorithm will go infinite."""

        #stash the key facts locally for speed
        canvas = self.canv
        style = self.style
        lines = self._cache['lines']
        leading = style.leading
        autoLeading = getattr(self,'autoLeading',getattr(style,'autoLeading',''))

        #work out the origin for line 1
        leftIndent = style.leftIndent
        cur_x = leftIndent

        if debug:
            bw = 0.5
            bc = Color(1,1,0)
            bg = Color(0.9,0.9,0.9)
        else:
            bw = getattr(style,'borderWidth',None)
            bc = getattr(style,'borderColor',None)
            bg = style.backColor

        #if has a background or border, draw it
        if bg or (bc and bw):
            canvas.saveState()
            op = canvas.rect
            kwds = dict(fill=0,stroke=0)
            if bc and bw:
                canvas.setStrokeColor(bc)
                canvas.setLineWidth(bw)
                kwds['stroke'] = 1
                br = getattr(style,'borderRadius',0)
                if br and not debug:
                    op = canvas.roundRect
                    kwds['radius'] = br
            if bg:
                canvas.setFillColor(bg)
                kwds['fill'] = 1
            bp = getattr(style,'borderPadding',0)
            op(leftIndent-bp,
                        -bp,
                        self.width - (leftIndent+style.rightIndent)+2*bp,
                        self.height+2*bp,
                        **kwds)
            canvas.restoreState()

        #print "Lines: %s" % lines
        nLines = len(lines)
        #print "len(lines)", nLines
        bulletText = self.bulletText
        if nLines > 0:
            _offsets = getattr(self,'_offsets',[0])
            _offsets += (nLines-len(_offsets))*[_offsets[-1]]
            canvas.saveState()
            #canvas.addLiteral('%% %s.drawPara' % _className(self))
            alignment = style.alignment
            offset = style.firstLineIndent+_offsets[0]
            lim = nLines-1
            noJustifyLast = not (hasattr(self,'_JustifyLast') and self._JustifyLast)
            f = lines[0]
            #cur_y = self.height - getattr(f,'ascent',f.fontSize)
            cur_y = sum([line.height for line in lines]) - f.ascent

            # default?
            dpl = self._leftDrawParaLineX
            if bulletText:
                oo = offset
                offset = _drawBullet(canvas,offset,cur_y,bulletText,style)
            if alignment == TA_LEFT:
                dpl = self._leftDrawParaLineX
            elif alignment == TA_CENTER:
                dpl = self._centerDrawParaLineX
            elif self.style.alignment == TA_RIGHT:
                dpl = self._rightDrawParaLineX
            elif self.style.alignment == TA_JUSTIFY:
                dpl = self._justifyDrawParaLineX
            else:
                raise ValueError("bad align %s" % repr(alignment))

            #set up the font etc.
            tx = self.beginText(cur_x, cur_y)
            xs = tx.XtraState=ABag()
            xs.textColor=None
            xs.rise=0
            xs.underline=0
            xs.underlines=[]
            xs.underlineColor=None
            xs.backgrounds = []
            xs.backgroundColor = None
            xs.backgroundFontSize = None
            xs.strike=0
            xs.strikes=[]
            xs.strikeColor=None
            xs.links=[]
            xs.link=None
            xs.leading = style.leading
            xs.leftIndent = leftIndent
            tx._leading = None
            tx._olb = None
            xs.cur_y = cur_y
            xs.f = f
            xs.style = style
            xs.autoLeading = autoLeading

            tx._fontname,tx._fontsize = None, None
            dpl( tx, offset, lines[0], noJustifyLast and nLines==1)
            _do_post_text(tx)

            #now the middle of the paragraph, aligned with the left margin which is our origin.
            for i in xrange(1, nLines):
                f = lines[i]
                dpl( tx, _offsets[i], f, noJustifyLast and i==lim)
                _do_post_text(tx)

            canvas.drawText(tx)
            canvas.restoreState()

    def _leftDrawParaLineX( self, tx, offset, line, last=0):
        if line.space_wasted < 0:
            return self._justifyDrawParaLineX(tx,offset,line,last)
        setXPos(tx,offset)
        _putFragLine(offset, tx, line)
        setXPos(tx,-offset)

    def _rightDrawParaLineX( self, tx, offset, line, last=0):
        if line.space_wasted < 0:
            return self._justifyDrawParaLineX(tx,offset,line,last)
        m = offset + line.space_wasted
        setXPos(tx,m)
        _putFragLine(m, tx, line)
        setXPos(tx,-m)

    def _centerDrawParaLineX( self, tx, offset, line, last=0):
        if line.space_wasted < 0:
            return self._justifyDrawParaLineX(tx,offset,line,last)
        m = offset + 0.5 * line.space_wasted
        setXPos(tx, m)
        _putFragLine(m, tx, line)
        setXPos(tx,-m)

    def _justifyDrawParaLineX( self, tx, offset, line, last=0):
        setXPos(tx,offset)
        frags = line.fragments[:]
        while frags and (not frags[0].width or isinstance(frags[0], StyledSpace)):
            frags.pop(0)
        while frags and (not frags[-1].width or isinstance(frags[-1], StyledSpace)):
            frags.pop()

        nSpaces = sum([len(frag.text) for frag in frags if isinstance(frag, StyledSpace)])
        # TODO: if !nSpaces use txt.setCharSpace instead
        if last or not nSpaces or abs(line.space_wasted)<=1e-8 or isinstance(frags[-1], StyledNewLine):
            _putFragLine(offset, tx, line)  #no space modification
        else:
            tx.setWordSpace(line.space_wasted / float(nSpaces))
            _putFragLine(offset, tx, line)
            tx.setWordSpace(0)
        setXPos(tx,-offset)

    class OVERFLOW:
         pass
    class SQUEEZE:
         pass
    class HYPHENATE:
         pass

    def rateHyph(self, base_penalty, frags, word, space_remaining):
        """Rate a possible hyphenation point"""
        #### The rating could be wrong, in particular if space_remaining is too small!
        #print "rateHyph frags=%s, word=%r, space_remaining=%d" % (frags,word, space_remaining)
        # All the factors used here are just a wild guess
        spaces_width = sum([frag.width for frag in frags if isinstance(frag, StyledSpace)])
        if spaces_width:
            stretch = space_remaining/spaces_width
            if stretch<0:
                stretch_penalty = stretch*stretch*stretch*stretch*5000
            else:
                stretch_penalty = stretch*stretch*30
        else: # HVB 20060907: Not a single space so far
            if space_remaining > 0:
                # TODO this should be easier
                lst = [(len(frag.text), frag,width) for frag in frags if hasattr(frag,"text")]
                sum_len = sum([x[0] for x in lst])
                sum_width=sum([x[1] for x in lst])
                if sum_len > 0:
                    avg_char_width = sum_width / sum_len
                    stretch_penalty = space_remaining/avg_char_width*20
                else:
                    stretch_penalty = space_remaining*60
            else:
                 stretch_penalty = 20000
        rating = 16384 - base_penalty - stretch_penalty
        #print "  rating:", rating
        return rating

    # finding bestSolution where the word uses possibly several different font styles
    # (action,left,right,spaceWasted) = self.findBestSolution(frags,w,currentWidth,maxWidth,windx<len(words))
    def findBestSolution(self, frags, word, space_remaining, try_squeeze):
        assert isinstance(word, StyledWord)
        assert space_remaining <= word.width
        if self.style.hyphenation and not hasattr(word, "nobr"):
            hyphenator = wordaxe.hyphRegistry.get(self.style.language,None)
        else:
            # Hyphenation deactivated
            hyphenator = None

        #print "findBestSolution %s %s %s" % (frags, word, space_remaining)
        nwords = len([True for frag in frags if isinstance(frag,StyledWord)])
        #print "nwords=%d" % nwords
        if hyphenator is None:
            # The old RL way: at least one word per line
            if nwords:
                return (self.OVERFLOW, None, word, space_remaining)
            else:
                return (self.SQUEEZE, word, None, space_remaining - word.width)
        if not isinstance(word.text, HyphenatedWord):
            hw = hyphenator.hyphenate(word.text)
            if not hw: hw = HyphenatedWord(word.text, hyphenations=list())
            word.text = hw
        assert isinstance(word.text, HyphenatedWord)
        #print "hyphenations:", word.text.hyphenations

        # try OVERFLOW
        quality = self.rateHyph(0, frags, None, space_remaining)
        bestSolution = (self.OVERFLOW, None, word, space_remaining)
        #print "OV"
        # try SQUEEZE
        if try_squeeze and nwords: # HVB 20080925 why "and nwords"?
            q = self.rateHyph(0, frags, word, space_remaining - word.width)
            if q>quality:
                #print "SQZ"
                bestSolution = (self.SQUEEZE, word, None, space_remaining - word.width)
                quality = q
        # try HYPHENATE
        for hp in word.text.hyphenations:
            left,right = word.splitAt(hp)
            #print "left=%r  right=%r" % (left, right)
            q = self.rateHyph(100-10*hp.quality,frags,left,space_remaining - left.width)
            if q>quality:
                bestSolution = (self.HYPHENATE, left, right, space_remaining - left.width)
                quality = q
        if bestSolution[0] is self.OVERFLOW and not nwords:
            # We have to make a hard break in the word
            #print "FORCE Hyphenation"
            # force at least a single character into this line
            left, right = word.splitAt(HyphenationPoint(1,1,0,"",0,""))
            bestSolution = (self.HYPHENATE, left, right, 0)
            for p in range(1,len(word.text)):
                if word.text[p-1] not in ["-",SHY]:
                    r = SHY
                else:
                    r = ""
                left,right = word.splitAt(HyphenationPoint(p,1,0,r,0,""))
                if left.width <= space_remaining:
                    bestSolution = (self.HYPHENATE, left, right, space_remaining - left.width)
                else:
                    # does not fit anymore
                    break

        #print "bestSolution for", word, "returns:", HVBDBG.s(bestSolution)
        return bestSolution


    def getPlainText(self,identify=None):
        """Convenience function for templates which want access
        to the raw text, without XML tags.

        Note: will only get the first part if a paragraph is splitted.
        This is not perfect, but should work good enough to be used for the TOC.
        """
        text = []
        lines = self._cache.get('lines')
        if lines is not None:
            for line in lines:
                if line is not lines[0]:
                    text.append(" ")
                for frag in line.fragments:
                    if hasattr(frag, "text"):
                        text.append(getattr(frag, "text"))
        else:
            for frag in self.frags:
                if hasattr(frag, "text"):
                    text.append(getattr(frag, "text"))
        return "".join(text)

    def minWidth(self):
        """Attempt to determine a minimum sensible width"""
        return max([frag.width for frag in self.frags])


class ParagraphAndImage(Flowable):
    '''combine a Paragraph and an Image'''
    def __init__(self,P,I,xpad=3,ypad=3,side='right'):
        self.P = P
        self.I = I
        self.xpad = xpad
        self.ypad = ypad
        self._side = side

    def getSpaceBefore(self):
        return max(self.P.getSpaceBefore(),self.I.getSpaceBefore())

    def getSpaceAfter(self):
        return max(self.P.getSpaceAfter(),self.I.getSpaceAfter())

    def wrap(self,availWidth,availHeight):
        wI, hI = self.I.wrap(availWidth,availHeight)
        self.wI = wI
        self.hI = hI
        # work out widths array for breaking
        self.width = availWidth
        P = self.P
        style = P.style
        xpad = self.xpad
        ypad = self.ypad
        leading = style.leading
        leftIndent = style.leftIndent
        later_widths = availWidth - leftIndent - style.rightIndent
        intermediate_widths = later_widths - xpad - wI
        first_line_width = intermediate_widths - style.firstLineIndent
        P.width = 0
        nIW = int((hI+ypad)/leading)

        if 'avail' in P._cache:
            ph = P.height
        else:
            max_widths = [first_line_width] + nIW*[intermediate_widths] + [later_widths]
            pw, ph = P.i_wrap(availWidth, availHeight, max_widths)
        if self._side=='left':
            self._offsets = [wI+xpad]*(1+nIW)+[0]
        self.height = max(hI,ph)
        return (self.width, self.height)

    def split(self,availWidth, availHeight):
        P, wI, hI, ypad = self.P, self.wI, self.hI, self.ypad
        if hI+ypad>availHeight or len(P.frags)<=0: return []
        S = P.split(availWidth,availHeight)
        #print S
        if not S: return S
        P = self.P = S[0]
        del S[0]
        style = P.style
        #P.height = len(self.P.blPara.lines)*style.leading
        self.height = max(hI,P.height)
        return [self]+S

    def draw(self):
        canv = self.canv
        if self._side=='left':
            self.I.drawOn(canv,0,self.height-self.hI)
            self.P._offsets = self._offsets
            try:
                self.P.drawOn(canv,0,0)
            finally:
                del self.P._offsets
        else:
            self.I.drawOn(canv,self.width-self.wI-self.xpad,self.height-self.hI)
            self.P.drawOn(canv,0,0)


# from here on, only test code...

class HVBDBG:
    @staticmethod
    def s(obj):
        if type(obj) == list:
            return "[" + ", ".join([HVBDBG.s(x) for x in obj]) + "]"
        elif type(obj) == tuple:
            return "(" + ", ".join([HVBDBG.s(x) for x in obj]) + ")"
        elif isinstance(obj, ABag):
            return "ABag(.text=%r)" % obj.text
        elif type(obj) == float:
            return "%1.2f" % obj
        else:
            return repr(obj)

if __name__ == "__main__":


    # Test
    import styles
    styleSheet = styles.getSampleStyleSheet()
    style = styleSheet["Normal"]
    #text = "Der blau<b>e </b><br />Klaus"
    #p = Paragraph(text, style)
    #print "width=%f" % sum([f.width for f in p.frags if hasattr(f,"width")])
    #print "p=%r" % p

    #p = Paragraph("jetzt auch <font backcolor='yellow'>bunt</font>", style)
    #frags = p.frags
    #print repr(frags)
    #print repr(frags[-1].fragments[0].style)

    import os
    import sys
    import unittest

    from reportlab.lib.units import cm
    from reportlab.lib import pagesizes
    from reportlab.lib.styles import getSampleStyleSheet
    from reportlab.platypus import Frame, PageTemplate, BaseDocTemplate

    USE_HYPHENATION = True

    if USE_HYPHENATION:
        import wordaxe.rl.styles
        from wordaxe.DCWHyphenator import DCWHyphenator
        wordaxe.hyphRegistry['DE'] = DCWHyphenator('DE', 5)

    PAGESIZE = pagesizes.landscape(pagesizes.A4)

    class TwoColumnDocTemplate(BaseDocTemplate):
        "Define a simple, two column document."

        def __init__(self, filename, **kw):
            m = 2*cm
            cw, ch = (PAGESIZE[0]-2*m)/2., (PAGESIZE[1]-2*m)
            f1 = Frame(m, m+0.5*cm, cw-0.75*cm, ch-1*cm, id='F1',
                leftPadding=0, topPadding=0, rightPadding=0, bottomPadding=0,
                showBoundary=True
            )
            f2 = Frame(cw+2.7*cm, m+0.5*cm, cw-0.75*cm, ch-1*cm, id='F2',
                leftPadding=0, topPadding=0, rightPadding=0, bottomPadding=0,
                showBoundary=True
            )
            apply(BaseDocTemplate.__init__, (self, filename), kw)
            template = PageTemplate('template', [f1, f2])
            self.addPageTemplates(template)

    def test():
        from reportlab.platypus.paragraph import Paragraph as platypus_Paragraph
        from wordaxe.DCWHyphenator import DCWHyphenator
        wordaxe.hyphRegistry["DE"] = DCWHyphenator("DE")
        stylesheet = getSampleStyleSheet()
        for indx, klass in enumerate([Paragraph, platypus_Paragraph]):
            normal = stylesheet['BodyText']
            normal.fontName = "Helvetica"
            normal.fontSize = 12
            normal.leading = 16
            if klass is Paragraph:
                normal.language = 'DE'
                normal.hyphenation = True
            normal.alignment = TA_JUSTIFY
            normal.firstLineIndent = 15*pt
            normal.leftIndent = 20*pt

            text = """Bedauerlicherweise ist ein <u>Donau</u>dampfschiffkapitän auch nur ein <a href="http://www.reportlab.org">Dampfschiff</a>kapitän."""
            # strange behaviour when next line uncommented
            text = " ".join(['<font color="red">%s</font>' % w for w in text.split()])

            text="""Das jeweils aktuelle Release der Software kann aber von der entsprechenden
    SourceForge <a color="blue" backColor="yellow" href="http://sourceforge.net/project/showfiles.php?group_id=105867">Download-Seite</a>
    heruntergeladen werden. Die allerneueste in Entwicklung befindliche Version
    wird im Sourceforge Subversion-Repository verwaltet.
    """.replace("\n"," ")

            story = []
            #story.append(Paragraph(text, style=normal))
            story.append(klass(u"Eine <font backcolor='yellow'>Aufzählung</font>, bei der der <font backcolor='green'>Text</font> hoffentlich etwas länger als eine Zeile ist.", style=normal, bulletText="\xe2\x80\xa2"))
            #story.append(klass(u"Silbentrennungsverfahren helfen dabei, extrem lange Donaudampfschiffe in handliche Schiffchen aufzuteilen. " * 10, style=normal))
            #story.append(klass(u"Silbentrennungsverfahren helfen dabei, extrem lange Donaudampfschiffe in handliche Schiffchen aufzuteilen.", style=normal, bulletText="\xe2\x80\xa2"))
            doc = TwoColumnDocTemplate(("test_NewParagraph_%d.pdf" %indx), pagesize=PAGESIZE)
            doc.build(story)

    test()