~openerp-groupes/openobject-server/6.0-fix-setup-windows

« back to all changes in this revision

Viewing changes to bin/reportlab/platypus/para.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""new experimental paragraph implementation
 
2
 
 
3
Intended to allow support for paragraphs in paragraphs, hotlinks,
 
4
embedded flowables, and underlining.  The main entry point is the
 
5
function
 
6
 
 
7
def Paragraph(text, style, bulletText=None, frags=None)
 
8
 
 
9
Which is intended to be plug compatible with the "usual" platypus
 
10
paragraph except that it supports more functionality.
 
11
 
 
12
In this implementation you may embed paragraphs inside paragraphs
 
13
to create hierarchically organized documents.
 
14
 
 
15
This implementation adds the following paragraph-like tags (which
 
16
support the same attributes as paragraphs, for font specification, etc).
 
17
 
 
18
- Unnumberred lists (ala html):
 
19
 
 
20
    <ul>
 
21
        <li>first one</li>
 
22
        <li>second one</li>
 
23
    </ul>
 
24
 
 
25
Also <ul type="disc"> (default) or <ul type="circle">, <ul type="square">.
 
26
 
 
27
- Numberred lists (ala html):
 
28
 
 
29
    <ol>
 
30
        <li>first one</li>
 
31
        <li>second one</li>
 
32
    </ol>
 
33
 
 
34
Also <ul type="1"> (default) or <ul type="a">, <ul type="A">.
 
35
 
 
36
- Display lists (ala HTML):
 
37
 
 
38
For example
 
39
 
 
40
<dl bulletFontName="Helvetica-BoldOblique" spaceBefore="10" spaceAfter="10">
 
41
<dt>frogs</dt> <dd>Little green slimy things. Delicious with <b>garlic</b></dd>
 
42
<dt>kittens</dt> <dd>cute, furry, not edible</dd>
 
43
<dt>bunnies</dt> <dd>cute, furry,. Delicious with <b>garlic</b></dd>
 
44
</dl>
 
45
 
 
46
ALSO the following additional internal paragraph markup tags are supported
 
47
 
 
48
<u>underlined text</u>
 
49
 
 
50
<a href="http://www.reportlab.com">hyperlinked text</a>
 
51
<a href="http://www.reportlab.com" color="blue">hyperlinked text</a>
 
52
 
 
53
<link destination="end" >Go to the end (go to document internal destination)</link>
 
54
<link destination="start" color="cyan">Go to the beginning</link>
 
55
 
 
56
<setLink destination="start" color="magenta">This is the document start
 
57
  (define document destination inside paragraph, color is optional)</setLink>
 
58
 
 
59
"""
 
60
 
 
61
from reportlab.pdfbase.pdfmetrics import stringWidth
 
62
from reportlab.lib.utils import fp_str
 
63
from reportlab.platypus.flowables import Flowable
 
64
from reportlab.lib import colors
 
65
 
 
66
from types import StringType, UnicodeType, InstanceType, TupleType, ListType, FloatType
 
67
 
 
68
# SET THIS TO CAUSE A VIEWING BUG WITH ACROREAD 3 (for at least one input)
 
69
# CAUSEERROR = 0
 
70
 
 
71
debug = 0
 
72
 
 
73
DUMPPROGRAM = 0
 
74
 
 
75
TOOSMALLSPACE = 1e-5
 
76
 
 
77
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
 
78
 
 
79
# indent changes effect the next line
 
80
# align changes effect the current line
 
81
 
 
82
# need to fix spacing questions... if ends with space then space may be inserted
 
83
 
 
84
# NEGATIVE SPACE SHOULD NEVER BE EXPANDED (IN JUSTIFICATION, EG)
 
85
 
 
86
class paragraphEngine:
 
87
    # text origin of 0,0 is upper left corner
 
88
    def __init__(self, program = None):
 
89
        from reportlab.lib.colors import black
 
90
        if program is None:
 
91
            program = []
 
92
        self.lineOpHandlers = [] # for handling underlining and hyperlinking, etc
 
93
        self.program = program
 
94
        self.indent = self.rightIndent = 0.0
 
95
        self.baseindent = 0.0 # adjust this to add more indentation for bullets, eg
 
96
        self.fontName = "Helvetica"
 
97
        self.fontSize = 10
 
98
        self.leading = 12
 
99
        self.fontColor = black
 
100
        self.x = self.y = self.rise = 0.0
 
101
        from reportlab.lib.enums import TA_LEFT
 
102
        self.alignment = TA_LEFT
 
103
        self.textStateStack = []
 
104
 
 
105
    TEXT_STATE_VARIABLES = ("indent", "rightIndent", "fontName", "fontSize",
 
106
                            "leading", "fontColor", "lineOpHandlers", "rise",
 
107
                            "alignment")
 
108
                            #"textStateStack")
 
109
 
 
110
    def pushTextState(self):
 
111
        state = []
 
112
        for var in self.TEXT_STATE_VARIABLES:
 
113
            val = getattr(self, var)
 
114
            state.append(val)
 
115
        #self.textStateStack.append(state)
 
116
        self.textStateStack = self.textStateStack+[state] # fresh copy
 
117
        #print "push", self.textStateStack
 
118
        #print "push", len(self.textStateStack), state
 
119
        return state
 
120
 
 
121
    def popTextState(self):
 
122
        state = self.textStateStack[-1]
 
123
        self.textStateStack = self.textStateStack[:-1]
 
124
        #print "pop", self.textStateStack
 
125
        state = state[:] # copy for destruction
 
126
        #print "pop", len(self.textStateStack), state
 
127
        #print "handlers before", self.lineOpHandlers
 
128
        for var in self.TEXT_STATE_VARIABLES:
 
129
            val = state[0]
 
130
            del state[0]
 
131
            setattr(self, var, val)
 
132
 
 
133
    def format(self, maxwidth, maxheight, program, leading=0):
 
134
        "return program with line operations added if at least one line fits"
 
135
        # note: a generated formatted segment should not be formatted again
 
136
        startstate = self.__dict__.copy()
 
137
        #remainder = self.cleanProgram(program)
 
138
        remainder = program[:]
 
139
        #program1 = remainder[:] # debug only
 
140
        lineprogram = []
 
141
        #if maxheight<TOOSMALLSPACE:
 
142
        #    raise ValueError, "attempt to format inside too small a height! "+repr(maxheight)
 
143
        heightremaining = maxheight
 
144
        if leading: self.leading = leading
 
145
        room = 1
 
146
        cursorcount = 0 # debug
 
147
        while remainder and room: #heightremaining>=self.leading and remainder:
 
148
            #print "getting line with statestack", len(self.textStateStack)
 
149
            #heightremaining = heightremaining - self.leading
 
150
            indent = self.indent
 
151
            rightIndent = self.rightIndent
 
152
            linewidth = maxwidth - indent - rightIndent
 
153
            beforelinestate = self.__dict__.copy()
 
154
            if linewidth<TOOSMALLSPACE:
 
155
                raise ValueError, "indents %s %s too wide for space %s" % (self.indent, self.rightIndent, \
 
156
                                                                           maxwidth)
 
157
            try:
 
158
                (lineIsFull, line, cursor, currentLength, \
 
159
                 usedIndent, maxLength, justStrings) = self.fitLine(remainder, maxwidth)
 
160
            except:
 
161
##                print "failed to fit line near", cursorcount # debug
 
162
##                for i in program1[max(0,cursorcount-10): cursorcount]:
 
163
##                    print
 
164
##                    print i,
 
165
##                print "***" *8
 
166
##                for i in program1[cursorcount:cursorcount+20]:
 
167
##                    print i
 
168
                raise
 
169
            cursorcount = cursorcount+cursor # debug
 
170
            leading = self.leading
 
171
            if heightremaining>leading:
 
172
                heightremaining = heightremaining-leading
 
173
            else:
 
174
                room = 0
 
175
                #self.resetState(beforelinestate)
 
176
                self.__dict__.update(beforelinestate)
 
177
                break # no room for this line
 
178
##            if debug:
 
179
##                print "line", line
 
180
##                if lineIsFull: print "is full"
 
181
##                else: print "is partially full"
 
182
##                print "consumes", cursor, "elements"
 
183
##                print "covers", currentLength, "of", maxwidth
 
184
            alignment = self.alignment # last declared alignment for this line used
 
185
            # recompute linewidth using the used indent
 
186
            #linewidth = maxwidth - usedIndent - rightIndent
 
187
            remainder = remainder[cursor:]
 
188
            if not remainder:
 
189
                # trim off the extra end of line
 
190
                del line[-1]
 
191
            # do justification if any
 
192
            #line = self.shrinkWrap(line
 
193
            if alignment==TA_LEFT:
 
194
                #if debug:
 
195
                #    print "ALIGN LEFT"
 
196
                if justStrings:
 
197
                    line = stringLine(line, currentLength)
 
198
                else:
 
199
                    line = self.shrinkWrap(line)
 
200
                pass
 
201
            elif alignment==TA_CENTER:
 
202
                #if debug:
 
203
                #    print "ALIGN CENTER"
 
204
                if justStrings:
 
205
                    line = stringLine(line, currentLength)
 
206
                else:
 
207
                    line = self.shrinkWrap(line)
 
208
                line = self.centerAlign(line, currentLength, maxLength)
 
209
            elif alignment==TA_RIGHT:
 
210
                #if debug:
 
211
                #    print "ALIGN RIGHT"
 
212
                if justStrings:
 
213
                    line = stringLine(line, currentLength)
 
214
                else:
 
215
                    line = self.shrinkWrap(line)
 
216
                line = self.rightAlign(line, currentLength, maxLength)
 
217
            elif alignment==TA_JUSTIFY:
 
218
                #if debug:
 
219
                #    print "JUSTIFY"
 
220
                if remainder and lineIsFull:
 
221
                    if justStrings:
 
222
                        line = simpleJustifyAlign(line, currentLength, maxLength)
 
223
                    else:
 
224
                        line = self.justifyAlign(line, currentLength, maxLength)
 
225
                else:
 
226
                    if justStrings:
 
227
                        line = stringLine(line, currentLength)
 
228
                    else:
 
229
                        line = self.shrinkWrap(line)
 
230
                    if debug:
 
231
                        print "no justify because line is not full or end of para"
 
232
            else:
 
233
                raise ValueError, "bad alignment "+repr(alignment)
 
234
            if not justStrings:
 
235
                line = self.cleanProgram(line)
 
236
            lineprogram.extend(line)
 
237
        laststate = self.__dict__.copy()
 
238
        #self.resetState(startstate)
 
239
        self.__dict__.update(startstate)
 
240
        heightused = maxheight - heightremaining
 
241
        return (lineprogram, remainder, laststate, heightused)
 
242
 
 
243
    def getState(self):
 
244
        # inlined
 
245
        return self.__dict__.copy()
 
246
 
 
247
    def resetState(self, state):
 
248
        # primarily inlined
 
249
        self.__dict__.update(state)
 
250
 
 
251
##    def sizeOfWord(self, word):
 
252
##        inlineThisFunctionForEfficiency
 
253
##        return float(stringWidth(word, self.fontName, self.fontSize))
 
254
 
 
255
    def fitLine(self, program, totalLength):
 
256
        "fit words (and other things) onto a line"
 
257
        # assuming word lengths and spaces have not been yet added
 
258
        # fit words onto a line up to maxlength, adding spaces and respecting extra space
 
259
        from reportlab.pdfbase.pdfmetrics import stringWidth
 
260
        usedIndent = self.indent
 
261
        maxLength = totalLength - usedIndent - self.rightIndent
 
262
        done = 0
 
263
        line = []
 
264
        cursor = 0
 
265
        lineIsFull = 0
 
266
        currentLength = 0
 
267
        maxcursor = len(program)
 
268
        needspace = 0
 
269
        first = 1
 
270
        terminated = None
 
271
        fontName = self.fontName
 
272
        fontSize = self.fontSize
 
273
        spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ")
 
274
        justStrings = 1
 
275
        while not done and cursor<maxcursor:
 
276
            opcode = program[cursor]
 
277
            #if debug: print "opcode", cursor, opcode
 
278
            topcode = type(opcode)
 
279
            if topcode in (StringType, UnicodeType, InstanceType):
 
280
                lastneedspace = needspace
 
281
                needspace = 0
 
282
                if topcode is InstanceType:
 
283
                    justStrings = 0
 
284
                    width = opcode.width(self)
 
285
                    needspace = 0
 
286
                else:
 
287
                    saveopcode = opcode
 
288
                    opcode = opcode.strip()
 
289
                    if opcode:
 
290
                        width = stringWidth(opcode, fontName, fontSize)
 
291
                    else:
 
292
                        width = 0
 
293
                    if saveopcode and (width or currentLength):
 
294
                        # ignore white space at margin
 
295
                        needspace = (saveopcode[-1]==" ")
 
296
                    else:
 
297
                        needspace = 0
 
298
                fullwidth = width
 
299
                if lastneedspace:
 
300
                    #spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ")
 
301
                    fullwidth = width + spacewidth
 
302
                newlength = currentLength+fullwidth
 
303
                if newlength>maxLength and not first: # always do at least one thing
 
304
                    # this word won't fit
 
305
                    #if debug:
 
306
                    #    print "WORD", opcode, "wont fit, width", width, "fullwidth", fullwidth
 
307
                    #    print "   currentLength", currentLength, "newlength", newlength, "maxLength", maxLength
 
308
                    done = 1
 
309
                    lineIsFull = 1
 
310
                else:
 
311
                    # fit the word: add a space then the word
 
312
                    if lastneedspace:
 
313
                        line.append( spacewidth ) # expandable space: positive
 
314
                    if opcode:
 
315
                        line.append( opcode )
 
316
                    if abs(width)>TOOSMALLSPACE:
 
317
                        line.append( -width ) # non expanding space: negative
 
318
                        currentLength = newlength
 
319
                    #print line
 
320
                    #stop
 
321
                first = 0
 
322
            elif topcode is FloatType:
 
323
                justStrings = 0
 
324
                aopcode = abs(opcode) # negative means non expanding
 
325
                if aopcode>TOOSMALLSPACE:
 
326
                    nextLength = currentLength+aopcode
 
327
                    if nextLength>maxLength and not first: # always do at least one thing
 
328
                        #if debug: print "EXPLICIT spacer won't fit", maxLength, nextLength, opcode
 
329
                        done = 1
 
330
                    else:
 
331
                        if aopcode>TOOSMALLSPACE:
 
332
                            currentLength = nextLength
 
333
                            line.append(opcode)
 
334
                    first = 0
 
335
            elif topcode is  TupleType:
 
336
                justStrings = 0
 
337
                indicator = opcode[0]
 
338
                #line.append(opcode)
 
339
                if indicator=="nextLine":
 
340
                    # advance to nextLine
 
341
                    #(i, endallmarks) = opcode
 
342
                    line.append(opcode)
 
343
                    cursor = cursor+1 # consume this element
 
344
                    terminated = done = 1
 
345
                    #if debug:
 
346
                    #    print "nextLine encountered"
 
347
                elif indicator=="color":
 
348
                    # change fill color
 
349
                    oldcolor = self.fontColor
 
350
                    (i, colorname) = opcode
 
351
                    #print "opcode", opcode
 
352
                    if type(colorname) in (StringType, UnicodeType):
 
353
                        color = self.fontColor = getattr(colors, colorname)
 
354
                    else:
 
355
                        color = self.fontColor = colorname # assume its something sensible :)
 
356
                    line.append(opcode)
 
357
                elif indicator=="face":
 
358
                    # change font face
 
359
                    (i, fontname) = opcode
 
360
                    fontName = self.fontName = fontname
 
361
                    spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ")
 
362
                    line.append(opcode)
 
363
                elif indicator=="size":
 
364
                    # change font size
 
365
                    (i, fontsize) = opcode
 
366
                    size = abs(float(fontsize))
 
367
                    if type(fontsize) in (StringType, UnicodeType):
 
368
                        if fontsize[:1]=="+":
 
369
                            fontSize = self.fontSize = self.fontSize + size
 
370
                        elif fontsize[:1]=="-":
 
371
                            fontSize = self.fontSize = self.fontSize - size
 
372
                        else:
 
373
                            fontSize = self.fontSize = size
 
374
                    else:
 
375
                        fontSize = self.fontSize = size
 
376
                    spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ")
 
377
                    line.append(opcode)
 
378
                elif indicator=="leading":
 
379
                    # change font leading
 
380
                    (i, leading) = opcode
 
381
                    self.leading = leading
 
382
                    line.append(opcode)
 
383
                elif indicator=="indent":
 
384
                    # increase the indent
 
385
                    (i, increment) = opcode
 
386
                    indent = self.indent = self.indent + increment
 
387
                    if first:
 
388
                        usedIndent = max(indent, usedIndent)
 
389
                        maxLength = totalLength - usedIndent - self.rightIndent
 
390
                    line.append(opcode)
 
391
                elif indicator=="push":
 
392
                    self.pushTextState()
 
393
                    line.append(opcode)
 
394
                elif indicator=="pop":
 
395
                    try:
 
396
                        self.popTextState()
 
397
                    except:
 
398
##                        print "stack fault near", cursor
 
399
##                        for i in program[max(0, cursor-10):cursor+10]:
 
400
##                            if i==cursor:
 
401
##                                print "***>>>",
 
402
##                            print i
 
403
                        raise
 
404
                    fontName = self.fontName
 
405
                    fontSize = self.fontSize
 
406
                    spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ")
 
407
                    line.append(opcode)
 
408
                elif indicator=="bullet":
 
409
                    (i, bullet, indent, font, size) = opcode
 
410
                    # adjust for base indent (only at format time -- only execute once)
 
411
                    indent = indent + self.baseindent
 
412
                    opcode = (i, bullet, indent, font, size)
 
413
                    if not first:
 
414
                        raise ValueError, "bullet not at beginning of line"
 
415
                    bulletwidth = float(stringWidth(bullet, font, size))
 
416
                    spacewidth = float(stringWidth(" ", font, size))
 
417
                    bulletmin = indent+spacewidth+bulletwidth
 
418
                    # decrease the line size to allow bullet
 
419
                    usedIndent = max(bulletmin, usedIndent)
 
420
                    if first:
 
421
                        maxLength = totalLength - usedIndent - self.rightIndent
 
422
                    line.append(opcode)
 
423
                elif indicator=="rightIndent":
 
424
                    # increase the right indent
 
425
                    (i, increment) = opcode
 
426
                    self.rightIndent = self.rightIndent+increment
 
427
                    if first:
 
428
                        maxLength = totalLength - usedIndent - self.rightIndent
 
429
                    line.append(opcode)
 
430
                elif indicator=="rise":
 
431
                    (i, rise) = opcode
 
432
                    newrise = self.rise = self.rise+rise
 
433
                    line.append(opcode)
 
434
                elif indicator=="align":
 
435
                    (i, alignment) = opcode
 
436
                    #if debug:
 
437
                    #    print "SETTING ALIGNMENT", alignment
 
438
                    self.alignment = alignment
 
439
                    line.append(opcode)
 
440
                elif indicator=="lineOperation":
 
441
                    (i, handler) = opcode
 
442
                    line.append(opcode)
 
443
                    self.lineOpHandlers = self.lineOpHandlers + [handler] # fresh copy
 
444
                elif indicator=="endLineOperation":
 
445
                    (i, handler) = opcode
 
446
                    h = self.lineOpHandlers[:] # fresh copy
 
447
                    h.remove(handler)
 
448
                    self.lineOpHandlers = h
 
449
                    line.append(opcode)
 
450
 
 
451
                else:
 
452
                    raise ValueError, "at format time don't understand indicator "+repr(indicator)
 
453
            else:
 
454
                raise ValueError, "op must be string, float, instance, or tuple "+repr(opcode)
 
455
            if not done:
 
456
                cursor = cursor+1
 
457
                #first = 0
 
458
##            if debug:
 
459
##                if done:
 
460
##                    print "DONE FLAG IS SET"
 
461
##                if cursor>=maxcursor:
 
462
##                    print "AT END OF PROGRAM"
 
463
        if not terminated:
 
464
            line.append( ("nextLine", 0) )
 
465
        #print "fitline", line
 
466
        return (lineIsFull, line, cursor, currentLength, usedIndent, maxLength, justStrings)
 
467
 
 
468
    def centerAlign(self, line, lineLength, maxLength):
 
469
        diff = maxLength-lineLength
 
470
        shift = diff/2.0
 
471
        if shift>TOOSMALLSPACE:
 
472
            return self.insertShift(line, shift)
 
473
        return line
 
474
 
 
475
    def rightAlign(self, line, lineLength, maxLength):
 
476
        shift = maxLength-lineLength
 
477
        #die
 
478
        if shift>TOOSMALLSPACE:
 
479
            return self.insertShift(line, shift)
 
480
        return line
 
481
 
 
482
    def insertShift(self, line, shift):
 
483
        # insert shift just before first visible element in line
 
484
        result = []
 
485
        first = 1
 
486
        for e in line:
 
487
            te = type(e)
 
488
            if first and (te in (StringType, UnicodeType, InstanceType)):
 
489
                result.append(shift)
 
490
                first = 0
 
491
            result.append(e)
 
492
        return result
 
493
 
 
494
    def justifyAlign(self, line, lineLength, maxLength):
 
495
        diff = maxLength-lineLength
 
496
        # count EXPANDABLE SPACES AFTER THE FIRST VISIBLE
 
497
        spacecount = 0
 
498
        visible = 0
 
499
        for e in line:
 
500
            te = type(e)
 
501
            if te is FloatType and e>TOOSMALLSPACE and visible:
 
502
                spacecount = spacecount+1
 
503
            elif te in (StringType, UnicodeType, InstanceType):
 
504
                visible = 1
 
505
        #if debug: print "diff is", diff, "wordcount", wordcount #; die
 
506
        if spacecount<1:
 
507
            return line
 
508
        shift = diff/float(spacecount)
 
509
        if shift<=TOOSMALLSPACE:
 
510
            #if debug: print "shift too small", shift
 
511
            return line
 
512
        first = 1
 
513
        visible = 0
 
514
        result = []
 
515
        cursor = 0
 
516
        nline = len(line)
 
517
        while cursor<nline:
 
518
            e = line[cursor]
 
519
            te = type(e)
 
520
            result.append(e)
 
521
            if (te in (StringType, UnicodeType, InstanceType)):
 
522
                visible = 1
 
523
            elif te is FloatType and e>TOOSMALLSPACE and visible:
 
524
                expanded = e+shift
 
525
                result[-1] = expanded
 
526
            cursor = cursor+1
 
527
        return result
 
528
 
 
529
##                if not first:
 
530
##                    #if debug: print "shifting", shift, e
 
531
##                    #result.append(shift)
 
532
##                    # add the shift in result before any start markers before e
 
533
##                    insertplace = len(result)-1
 
534
##                    done = 0
 
535
##                    myshift = shift
 
536
##                    while insertplace>0 and not done:
 
537
##                        beforeplace = insertplace-1
 
538
##                        beforething = result[beforeplace]
 
539
##                        thingtype = type(beforething)
 
540
##                        if thingtype is TupleType:
 
541
##                            indicator = beforething[0]
 
542
##                            if indicator=="endLineOperation":
 
543
##                                done = 1
 
544
##                            elif debug:
 
545
##                                print "adding shift before", beforething
 
546
##                        elif thingtype is FloatType:
 
547
##                            myshift = myshift + beforething
 
548
##                            del result[beforeplace]
 
549
##                        else:
 
550
##                            done = 1
 
551
##                        if not done:
 
552
##                            insertplace = beforeplace
 
553
##                    result.insert(insertplace, myshift)
 
554
##                first = 0
 
555
##            cursor = cursor+1
 
556
##        return result
 
557
 
 
558
    def shrinkWrap(self, line):
 
559
        # for non justified text, collapse adjacent text/shift's into single operations
 
560
        result = []
 
561
        index = 0
 
562
        maxindex = len(line)
 
563
        while index<maxindex:
 
564
            e = line[index]
 
565
            te = type(e)
 
566
            if te in (StringType, UnicodeType) and index<maxindex-1:
 
567
                # collect strings and floats
 
568
                thestrings = [e]
 
569
                thefloats = 0.0
 
570
                index = index+1
 
571
                nexte = line[index]
 
572
                tnexte = type(nexte)
 
573
                while index<maxindex and (tnexte in (FloatType, StringType, UnicodeType)):
 
574
                    # switch to expandable space if appropriate
 
575
                    if tnexte is FloatType:
 
576
                        if thefloats<0 and nexte>0:
 
577
                            thefloats = -thefloats
 
578
                        if nexte<0 and thefloats>0:
 
579
                            nexte = -nexte
 
580
                        thefloats = thefloats + nexte
 
581
                    elif tnexte in (StringType, UnicodeType):
 
582
                        thestrings.append(nexte)
 
583
                    index = index+1
 
584
                    if index<maxindex:
 
585
                        nexte = line[index]
 
586
                        tnexte = type(nexte)
 
587
                # wrap up the result
 
588
                s = ' '.join(thestrings)
 
589
                result.append(s)
 
590
                result.append(float(thefloats))
 
591
                # back up for unhandled element
 
592
                index = index-1
 
593
            else:
 
594
                result.append(e)
 
595
            index = index+1
 
596
 
 
597
        return result
 
598
 
 
599
    def cleanProgram(self, line):
 
600
        "collapse adjacent spacings"
 
601
        #return line # for debugging
 
602
        result = []
 
603
        last = 0
 
604
        for e in line:
 
605
            if type(e) is FloatType:
 
606
                # switch to expandable space if appropriate
 
607
                if last<0 and e>0:
 
608
                    last = -last
 
609
                if e<0 and last>0:
 
610
                    e = -e
 
611
                last = float(last)+e
 
612
            else:
 
613
                if abs(last)>TOOSMALLSPACE:
 
614
                    result.append(last)
 
615
                result.append(e)
 
616
                last = 0
 
617
        if last:
 
618
            result.append(last)
 
619
        # now go backwards and delete all floats occurring after all visible elements
 
620
##        count = len(result)-1
 
621
##        done = 0
 
622
##        while count>0 and not done:
 
623
##            e = result[count]
 
624
##            te = type(e)
 
625
##            if te is StringType or te is InstanceType or te is TupleType:
 
626
##                done = 1
 
627
##            elif te is FloatType:
 
628
##                del result[count]
 
629
##            count = count-1
 
630
        # move end operations left and start operations left up to visibles
 
631
        change = 1
 
632
        rline = range(len(result)-1)
 
633
        while change:
 
634
            #print line
 
635
            change = 0
 
636
            for index in rline:
 
637
                nextindex = index+1
 
638
                this = result[index]
 
639
                next = result[nextindex]
 
640
                doswap = 0
 
641
                tthis = type(this)
 
642
                tnext = type(next)
 
643
                # don't swap visibles
 
644
                if tthis in (StringType, UnicodeType) or \
 
645
                   tnext in (StringType, UnicodeType) or \
 
646
                   this is InstanceType or tnext is InstanceType:
 
647
                    doswap = 0
 
648
                # only swap two tuples if the second one is an end operation and the first is something else
 
649
                elif tthis is TupleType:
 
650
                    thisindicator = this[0]
 
651
                    if tnext is TupleType:
 
652
                        nextindicator = next[0]
 
653
                        doswap = 0
 
654
                        if (nextindicator=="endLineOperation" and thisindicator!="endLineOperation"
 
655
                            and thisindicator!="lineOperation"):
 
656
                            doswap = 1 # swap nonend<>end
 
657
                    elif tnext==FloatType:
 
658
                        if thisindicator=="lineOperation":
 
659
                            doswap = 1 # begin <> space
 
660
                if doswap:
 
661
                    #print "swap", line[index],line[nextindex]
 
662
                    result[index] = next
 
663
                    result[nextindex] = this
 
664
                    change = 1
 
665
        return result
 
666
 
 
667
    def runOpCodes(self, program, canvas, textobject):
 
668
        "render the line(s)"
 
669
 
 
670
        escape = canvas._escape
 
671
        code = textobject._code
 
672
        startstate = self.__dict__.copy()
 
673
        font = None
 
674
        size = None
 
675
        # be sure to set them before using them (done lazily below)
 
676
        #textobject.setFont(self.fontName, self.fontSize)
 
677
        textobject.setFillColor(self.fontColor)
 
678
        xstart = self.x
 
679
        thislineindent = self.indent
 
680
        thislinerightIndent = self.rightIndent
 
681
        indented = 0
 
682
        for opcode in program:
 
683
            topcode = type(opcode)
 
684
            if topcode in (StringType, UnicodeType, InstanceType):
 
685
                if not indented:
 
686
                    if abs(thislineindent)>TOOSMALLSPACE:
 
687
                        #if debug: print "INDENTING", thislineindent
 
688
                        #textobject.moveCursor(thislineindent, 0)
 
689
                        code.append('%s Td' % fp_str(thislineindent, 0))
 
690
                        self.x = self.x + thislineindent
 
691
                    for handler in self.lineOpHandlers:
 
692
                        #handler.end_at(x, y, self, canvas, textobject) # finish, eg, underlining this line
 
693
                        handler.start_at(self.x, self.y, self, canvas, textobject) # start underlining the next
 
694
                indented = 1
 
695
                # lazily set font (don't do it again if not needed)
 
696
                if font!=self.fontName or size!=self.fontSize:
 
697
                    font = self.fontName
 
698
                    size = self.fontSize
 
699
                    textobject.setFont(font, size)
 
700
                if topcode in (StringType, UnicodeType):
 
701
                    #textobject.textOut(opcode)
 
702
                    text = escape(opcode)
 
703
                    code.append('(%s) Tj' % text)
 
704
                else:
 
705
                    # drawable thing
 
706
                    opcode.execute(self, textobject, canvas)
 
707
            elif topcode is FloatType:
 
708
                # use abs value (ignore expandable marking)
 
709
                opcode = abs(opcode)
 
710
                if opcode>TOOSMALLSPACE:
 
711
                    #textobject.moveCursor(opcode, 0)
 
712
                    code.append('%s Td' % fp_str(opcode, 0))
 
713
                    self.x = self.x + opcode
 
714
            elif topcode is TupleType:
 
715
                indicator = opcode[0]
 
716
                if indicator=="nextLine":
 
717
                    # advance to nextLine
 
718
                    (i, endallmarks) = opcode
 
719
                    x = self.x
 
720
                    y = self.y
 
721
                    newy = self.y = self.y-self.leading
 
722
                    newx = self.x = xstart
 
723
                    thislineindent = self.indent
 
724
                    thislinerightIndent = self.rightIndent
 
725
                    indented = 0
 
726
                    for handler in self.lineOpHandlers:
 
727
                        handler.end_at(x, y, self, canvas, textobject) # finish, eg, underlining this line
 
728
                        #handler.start_at(newx, newy, self, canvas, textobject)) # start underlining the next
 
729
                    textobject.setTextOrigin(newx, newy)
 
730
                elif indicator=="color":
 
731
                    # change fill color
 
732
                    oldcolor = self.fontColor
 
733
                    (i, colorname) = opcode
 
734
                    #print "opcode", opcode
 
735
                    if type(colorname) in (StringType, UnicodeType):
 
736
                        color = self.fontColor = getattr(colors, colorname)
 
737
                    else:
 
738
                        color = self.fontColor = colorname # assume its something sensible :)
 
739
                    #if debug:
 
740
                    #    print color.red, color.green, color.blue
 
741
                    #    print dir(color)
 
742
                    #print "color is", color
 
743
                    #from reportlab.lib.colors import green
 
744
                    #if color is green: print "color is green"
 
745
                    if color!=oldcolor:
 
746
                        textobject.setFillColor(color)
 
747
                elif indicator=="face":
 
748
                    # change font face
 
749
                    (i, fontname) = opcode
 
750
                    self.fontName = fontname
 
751
                    #textobject.setFont(self.fontName, self.fontSize)
 
752
                elif indicator=="size":
 
753
                    # change font size
 
754
                    (i, fontsize) = opcode
 
755
                    size = abs(float(fontsize))
 
756
                    if type(fontsize) in (StringType, UnicodeType):
 
757
                        if fontsize[:1]=="+":
 
758
                            fontSize = self.fontSize = self.fontSize + size
 
759
                        elif fontsize[:1]=="-":
 
760
                            fontSize = self.fontSize = self.fontSize - size
 
761
                        else:
 
762
                            fontSize = self.fontSize = size
 
763
                    else:
 
764
                        fontSize = self.fontSize = size
 
765
                    #(i, fontsize) = opcode
 
766
                    self.fontSize = fontSize
 
767
                    textobject.setFont(self.fontName, self.fontSize)
 
768
                elif indicator=="leading":
 
769
                    # change font leading
 
770
                    (i, leading) = opcode
 
771
                    self.leading = leading
 
772
                elif indicator=="indent":
 
773
                    # increase the indent
 
774
                    (i, increment) = opcode
 
775
                    indent = self.indent = self.indent + increment
 
776
                    thislineindent = max(thislineindent, indent)
 
777
                elif indicator=="push":
 
778
                    self.pushTextState()
 
779
                elif indicator=="pop":
 
780
                    oldcolor = self.fontColor
 
781
                    oldfont = self.fontName
 
782
                    oldsize = self.fontSize
 
783
                    self.popTextState()
 
784
                    #if CAUSEERROR or oldfont!=self.fontName or oldsize!=self.fontSize:
 
785
                    #    textobject.setFont(self.fontName, self.fontSize)
 
786
                    if oldcolor!=self.fontColor:
 
787
                        textobject.setFillColor(self.fontColor)
 
788
                elif indicator=="wordSpacing":
 
789
                    (i, ws) = opcode
 
790
                    textobject.setWordSpace(ws)
 
791
                elif indicator=="bullet":
 
792
                    (i, bullet, indent, font, size) = opcode
 
793
                    if abs(self.x-xstart)>TOOSMALLSPACE:
 
794
                        raise ValueError, "bullet not at beginning of line"
 
795
                    bulletwidth = float(stringWidth(bullet, font, size))
 
796
                    spacewidth = float(stringWidth(" ", font, size))
 
797
                    bulletmin = indent+spacewidth+bulletwidth
 
798
                    # decrease the line size to allow bullet as needed
 
799
                    if bulletmin > thislineindent:
 
800
                        #if debug: print "BULLET IS BIG", bullet, bulletmin, thislineindent
 
801
                        thislineindent = bulletmin
 
802
                    textobject.moveCursor(indent, 0)
 
803
                    textobject.setFont(font, size)
 
804
                    textobject.textOut(bullet)
 
805
                    textobject.moveCursor(-indent, 0)
 
806
                    #textobject.textOut("M")
 
807
                    textobject.setFont(self.fontName, self.fontSize)
 
808
                elif indicator=="rightIndent":
 
809
                    # increase the right indent
 
810
                    (i, increment) = opcode
 
811
                    self.rightIndent = self.rightIndent+increment
 
812
                elif indicator=="rise":
 
813
                    (i, rise) = opcode
 
814
                    newrise = self.rise = self.rise+rise
 
815
                    textobject.setRise(newrise)
 
816
                elif indicator=="align":
 
817
                    (i, alignment) = opcode
 
818
                    self.alignment = alignment
 
819
                elif indicator=="lineOperation":
 
820
                    (i, handler) = opcode
 
821
                    handler.start_at(self.x, self.y, self, canvas, textobject)
 
822
                    #self.lineOpHandlers.append(handler)
 
823
                    #if debug: print "adding", handler, self.lineOpHandlers
 
824
                    self.lineOpHandlers = self.lineOpHandlers + [handler] # fresh copy!
 
825
                elif indicator=="endLineOperation":
 
826
                    (i, handler) = opcode
 
827
                    handler.end_at(self.x, self.y, self, canvas, textobject)
 
828
                    newh = self.lineOpHandlers = self.lineOpHandlers[:] # fresh copy
 
829
                    #if debug: print "removing", handler, self.lineOpHandlers
 
830
                    if handler in newh:
 
831
                        self.lineOpHandlers.remove(handler)
 
832
                    else:
 
833
                        pass
 
834
                        #print "WARNING: HANDLER", handler, "NOT IN", newh
 
835
                else:
 
836
                    raise ValueError, "don't understand indicator "+repr(indicator)
 
837
            else:
 
838
                raise ValueError, "op must be string float or tuple "+repr(opcode)
 
839
        laststate = self.__dict__.copy()
 
840
        #self.resetState(startstate)
 
841
        self.__dict__.update(startstate)
 
842
        return laststate
 
843
 
 
844
def stringLine(line, length):
 
845
    "simple case: line with just strings and spacings which can be ignored"
 
846
 
 
847
    strings = []
 
848
    for x in line:
 
849
        if type(x) in (StringType, UnicodeType):
 
850
            strings.append(x)
 
851
    text = ' '.join(strings)
 
852
    result = [text, float(length)]
 
853
    nextlinemark = ("nextLine", 0)
 
854
    if line and line[-1]==nextlinemark:
 
855
        result.append( nextlinemark )
 
856
    return result
 
857
 
 
858
def simpleJustifyAlign(line, currentLength, maxLength):
 
859
    "simple justification with only strings"
 
860
 
 
861
    strings = []
 
862
    for x in line[:-1]:
 
863
        if type(x) in (StringType, UnicodeType):
 
864
            strings.append(x)
 
865
    nspaces = len(strings)-1
 
866
    slack = maxLength-currentLength
 
867
    text = ' '.join(strings)
 
868
    if nspaces>0 and slack>0:
 
869
        wordspacing = slack/float(nspaces)
 
870
        result = [("wordSpacing", wordspacing), text, maxLength, ("wordSpacing", 0)]
 
871
    else:
 
872
        result = [text, currentLength, ("nextLine", 0)]
 
873
    nextlinemark = ("nextLine", 0)
 
874
    if line and line[-1]==nextlinemark:
 
875
        result.append( nextlinemark )
 
876
    return result
 
877
 
 
878
from reportlab.lib.colors import black
 
879
 
 
880
def readBool(text):
 
881
    if text.upper() in ("Y", "YES", "TRUE", "1"):
 
882
        return 1
 
883
    elif text.upper() in ("N", "NO", "FALSE", "0"):
 
884
        return 0
 
885
    else:
 
886
        raise RMLError, "true/false attribute has illegal value '%s'" % text
 
887
 
 
888
def readAlignment(text):
 
889
    up = text.upper()
 
890
    if up == 'LEFT':
 
891
        return TA_LEFT
 
892
    elif up == 'RIGHT':
 
893
        return TA_RIGHT
 
894
    elif up in ['CENTER', 'CENTRE']:
 
895
        return TA_CENTER
 
896
    elif up == 'JUSTIFY':
 
897
        return TA_JUSTIFY
 
898
 
 
899
def readLength(text):
 
900
    """Read a dimension measurement: accept "3in", "5cm",
 
901
    "72 pt" and so on."""
 
902
    text = text.strip()
 
903
    try:
 
904
        return float(text)
 
905
    except ValueError:
 
906
        text = text.lower()
 
907
        numberText, units = text[:-2],text[-2:]
 
908
        numberText = numberText.strip()
 
909
        try:
 
910
            number = float(numberText)
 
911
        except ValueError:
 
912
            raise ValueError, "invalid length attribute '%s'" % text
 
913
        try:
 
914
            multiplier = {
 
915
                'in':72,
 
916
                'cm':28.3464566929,  #72/2.54; is this accurate?
 
917
                'mm':2.83464566929,
 
918
                'pt':1
 
919
                }[units]
 
920
        except KeyError:
 
921
            raise RMLError, "invalid length attribute '%s'" % text
 
922
 
 
923
        return number * multiplier
 
924
 
 
925
def lengthSequence(s, converter=readLength):
 
926
    """from "(2, 1)" or "2,1" return [2,1], for example"""
 
927
    s = s.strip()
 
928
    if s[:1]=="(" and s[-1:]==")":
 
929
        s = s[1:-1]
 
930
    sl = s.split(',')
 
931
    sl = [s.strip() for s in sl]
 
932
    sl = [converter(s) for s in sl]
 
933
    return sl
 
934
 
 
935
 
 
936
def readColor(text):
 
937
    """Read color names or tuples, RGB or CMYK, and return a Color object."""
 
938
    if not text:
 
939
        return None
 
940
    from reportlab.lib import colors
 
941
    from string import letters
 
942
    if text[0] in letters:
 
943
        return colors.__dict__[text]
 
944
    tup = lengthSequence(text)
 
945
 
 
946
    msg = "Color tuple must have 3 (or 4) elements for RGB (or CMYC)."
 
947
    assert 3 <= len(tup) <= 4, msg
 
948
    msg = "Color tuple must have all elements <= 1.0."
 
949
    for i in range(len(tup)):
 
950
        assert tup[i] <= 1.0, msg
 
951
 
 
952
    if len(tup) == 3:
 
953
        colClass = colors.Color
 
954
    elif len(tup) == 4:
 
955
        colClass = colors.CMYKColor
 
956
    return apply(colClass, tup)
 
957
 
 
958
class StyleAttributeConverters:
 
959
    fontSize=[readLength]
 
960
    leading=[readLength]
 
961
    leftIndent=[readLength]
 
962
    rightIndent=[readLength]
 
963
    firstLineIndent=[readLength]
 
964
    alignment=[readAlignment]
 
965
    spaceBefore=[readLength]
 
966
    spaceAfter=[readLength]
 
967
    bulletFontSize=[readLength]
 
968
    bulletIndent=[readLength]
 
969
    textColor=[readColor]
 
970
    backColor=[readColor]
 
971
 
 
972
class SimpleStyle:
 
973
    "simplified paragraph style without all the fancy stuff"
 
974
    name = "basic"
 
975
    fontName='Times-Roman'
 
976
    fontSize=10
 
977
    leading=12
 
978
    leftIndent=0
 
979
    rightIndent=0
 
980
    firstLineIndent=0
 
981
    alignment=TA_LEFT
 
982
    spaceBefore=0
 
983
    spaceAfter=0
 
984
    bulletFontName='Times-Roman'
 
985
    bulletFontSize=10
 
986
    bulletIndent=0
 
987
    textColor=black
 
988
    backColor=None
 
989
 
 
990
    def __init__(self, name, parent=None, **kw):
 
991
        mydict = self.__dict__
 
992
        if parent:
 
993
            for (a,b) in parent.__dict__.items():
 
994
                mydict[a]=b
 
995
        for (a,b) in kw.items():
 
996
            mydict[a] =  b
 
997
 
 
998
    def addAttributes(self, dictionary):
 
999
        for key in dictionary.keys():
 
1000
            value = dictionary[key]
 
1001
            if value is not None:
 
1002
                if hasattr(StyleAttributeConverters, key):
 
1003
                    converter = getattr(StyleAttributeConverters, key)[0]
 
1004
                    value = converter(value)
 
1005
                setattr(self, key, value)
 
1006
 
 
1007
 
 
1008
DEFAULT_ALIASES = {
 
1009
    "h1.defaultStyle": "Heading1",
 
1010
    "h2.defaultStyle": "Heading2",
 
1011
    "h3.defaultStyle": "Heading3",
 
1012
    "h4.defaultStyle": "Heading4",
 
1013
    "h5.defaultStyle": "Heading5",
 
1014
    "h6.defaultStyle": "Heading6",
 
1015
    "title.defaultStyle": "Title",
 
1016
    "subtitle.defaultStyle": "SubTitle",
 
1017
    "para.defaultStyle": "Normal",
 
1018
    "pre.defaultStyle": "Code",
 
1019
    "ul.defaultStyle": "UnorderedList",
 
1020
    "ol.defaultStyle": "OrderedList",
 
1021
    "li.defaultStyle": "Definition",
 
1022
    }
 
1023
 
 
1024
class FastPara(Flowable):
 
1025
    "paragraph with no special features (not even a single ampersand!)"
 
1026
 
 
1027
    def __init__(self, style, simpletext):
 
1028
        #if debug:
 
1029
        #    print "FAST", id(self)
 
1030
        if "&" in simpletext:
 
1031
            raise ValueError, "no ampersands please!"
 
1032
        self.style = style
 
1033
        self.simpletext = simpletext
 
1034
        self.lines = None
 
1035
 
 
1036
    def wrap(self, availableWidth, availableHeight):
 
1037
        simpletext = self.simpletext
 
1038
        self.availableWidth = availableWidth
 
1039
        style = self.style
 
1040
        text = self.simpletext
 
1041
        rightIndent = style.rightIndent
 
1042
        leftIndent = style.leftIndent
 
1043
        leading = style.leading
 
1044
        font = style.fontName
 
1045
        size = style.fontSize
 
1046
        firstindent = style.firstLineIndent
 
1047
        #textcolor = style.textColor
 
1048
        words = simpletext.split()
 
1049
        lines = []
 
1050
        from reportlab.pdfbase.pdfmetrics import stringWidth
 
1051
        spacewidth = stringWidth(" ", font, size)
 
1052
        currentline = []
 
1053
        currentlength = 0
 
1054
        firstmaxlength = availableWidth - rightIndent - firstindent
 
1055
        maxlength = availableWidth - rightIndent - leftIndent
 
1056
        if maxlength<spacewidth:
 
1057
            return (spacewidth+rightIndent+firstindent, availableHeight) # need something wider than this!
 
1058
        if availableHeight<leading:
 
1059
            return (availableWidth, leading) # need something longer
 
1060
        if self.lines is None:
 
1061
            heightused = 0
 
1062
            cursor = 0
 
1063
            nwords = len(words)
 
1064
            done = 0
 
1065
            #heightused = leading # ???
 
1066
            while cursor<nwords and not done:
 
1067
                thismaxlength = maxlength
 
1068
                if not lines:
 
1069
                    thismaxlength = firstmaxlength
 
1070
                thisword = words[cursor]
 
1071
                thiswordsize = stringWidth(thisword, font, size)
 
1072
                if currentlength:
 
1073
                    thiswordsize = thiswordsize+spacewidth
 
1074
                nextlength = currentlength + thiswordsize
 
1075
                if not currentlength or nextlength<maxlength:
 
1076
                    # add the word
 
1077
                    cursor = cursor+1
 
1078
                    currentlength = nextlength
 
1079
                    currentline.append(thisword)
 
1080
                    #print "currentline", currentline
 
1081
                else:
 
1082
                    # emit the line
 
1083
                    lines.append( (' '.join(currentline), currentlength, len(currentline)) )
 
1084
                    currentline = []
 
1085
                    currentlength = 0
 
1086
                    heightused = heightused+leading
 
1087
                    if heightused+leading>availableHeight:
 
1088
                        done = 1
 
1089
            if currentlength and not done:
 
1090
                lines.append( (' '.join(currentline), currentlength, len(currentline) ))
 
1091
                heightused = heightused+leading
 
1092
            self.lines = lines
 
1093
            self.height = heightused
 
1094
            remainder = self.remainder = ' '.join(words[cursor:])
 
1095
            #print "lines", lines
 
1096
            #print "remainder is", remainder
 
1097
        else:
 
1098
            remainder = None
 
1099
            heightused = self.height
 
1100
            lines = self.lines
 
1101
        if remainder:
 
1102
            result = (availableWidth, availableHeight+leading) # need to split
 
1103
        else:
 
1104
            result = (availableWidth, heightused)
 
1105
        #if debug: print "wrap is", (availableWidth, availableHeight), result, len(lines)
 
1106
        return result
 
1107
 
 
1108
    def split(self, availableWidth, availableHeight):
 
1109
        style = self.style
 
1110
        leading = style.leading
 
1111
        if availableHeight<leading:
 
1112
            return [] # not enough space for split
 
1113
        lines = self.lines
 
1114
        if lines is None:
 
1115
            raise ValueError, "must wrap before split"
 
1116
        remainder = self.remainder
 
1117
        if remainder:
 
1118
            next = FastPara(style, remainder)
 
1119
            return [self,next]
 
1120
        else:
 
1121
            return [self]
 
1122
 
 
1123
    def draw(self):
 
1124
        style = self.style
 
1125
        lines = self.lines
 
1126
        rightIndent = style.rightIndent
 
1127
        leftIndent = style.leftIndent
 
1128
        leading = style.leading
 
1129
        font = style.fontName
 
1130
        size = style.fontSize
 
1131
        alignment = style.alignment
 
1132
        firstindent = style.firstLineIndent
 
1133
        c = self.canv
 
1134
        escape = c._escape
 
1135
        #if debug:
 
1136
        #    print "FAST", id(self), "page number", c.getPageNumber()
 
1137
        height = self.height
 
1138
        #if debug:
 
1139
        #    c.rect(0,0,-1, height-size, fill=1, stroke=1)
 
1140
        c.translate(0, height-size)
 
1141
        textobject = c.beginText()
 
1142
        code = textobject._code
 
1143
        #textobject.setTextOrigin(0,firstindent)
 
1144
        textobject.setFont(font, size)
 
1145
        if style.textColor:
 
1146
            textobject.setFillColor(style.textColor)
 
1147
        first = 1
 
1148
        y = 0
 
1149
        basicWidth = self.availableWidth - rightIndent
 
1150
        count = 0
 
1151
        nlines = len(lines)
 
1152
        while count<nlines:
 
1153
            (text, length, nwords) = lines[count]
 
1154
            count = count+1
 
1155
            thisindent = leftIndent
 
1156
            if first:
 
1157
                thisindent = firstindent
 
1158
            if alignment==TA_LEFT:
 
1159
                x = thisindent
 
1160
            elif alignment==TA_CENTER:
 
1161
                extra = basicWidth - length
 
1162
                x = thisindent + extra/2.0
 
1163
            elif alignment==TA_RIGHT:
 
1164
                extra = basicWidth - length
 
1165
                x = thisindent + extra
 
1166
            elif alignment==TA_JUSTIFY:
 
1167
                x = thisindent
 
1168
                if count<nlines and nwords>1:
 
1169
                    # patch from doug@pennatus.com, 9 Nov 2002, no extraspace on last line
 
1170
                    textobject.setWordSpace((basicWidth-length)/(nwords-1.0))
 
1171
                else:
 
1172
                    textobject.setWordSpace(0.0)
 
1173
            textobject.setTextOrigin(x,y)
 
1174
            text = escape(text)
 
1175
            code.append('(%s) Tj' % text)
 
1176
            #textobject.textOut(text)
 
1177
            y = y-leading
 
1178
        c.drawText(textobject)
 
1179
 
 
1180
    def getSpaceBefore(self):
 
1181
        #if debug:
 
1182
        #    print "got space before", self.spaceBefore
 
1183
        return self.style.spaceBefore
 
1184
 
 
1185
    def getSpaceAfter(self):
 
1186
        #print "got space after", self.spaceAfter
 
1187
        return self.style.spaceAfter
 
1188
 
 
1189
def defaultContext():
 
1190
    result = {}
 
1191
    from reportlab.lib.styles import getSampleStyleSheet
 
1192
    styles = getSampleStyleSheet()
 
1193
    for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items():
 
1194
        result[stylenamekey] = styles[stylenamevalue]
 
1195
    return result
 
1196
 
 
1197
def buildContext(stylesheet=None):
 
1198
    result = {}
 
1199
    from reportlab.lib.styles import getSampleStyleSheet
 
1200
    if stylesheet is not None:
 
1201
        # Copy styles with the same name as aliases
 
1202
        for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items():
 
1203
            if stylesheet.has_key(stylenamekey):
 
1204
                result[stylenamekey] = stylesheet[stylenamekey]
 
1205
        # Then make aliases
 
1206
        for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items():
 
1207
            if stylesheet.has_key(stylenamevalue):
 
1208
                result[stylenamekey] = stylesheet[stylenamevalue]
 
1209
 
 
1210
    styles = getSampleStyleSheet()
 
1211
    # Then, fill in defaults if they were not filled yet.
 
1212
    for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items():
 
1213
        if not result.has_key(stylenamekey) and styles.has_key(stylenamevalue):
 
1214
            result[stylenamekey] = styles[stylenamevalue]
 
1215
    return result
 
1216
 
 
1217
class Para(Flowable):
 
1218
 
 
1219
    spaceBefore = 0
 
1220
    spaceAfter = 0
 
1221
 
 
1222
    def __init__(self, style, parsedText=None, bulletText=None, state=None, context=None, baseindent=0):
 
1223
        #print id(self), "para", parsedText
 
1224
        self.baseindent = baseindent
 
1225
        self.context = buildContext(context)
 
1226
        self.parsedText = parsedText
 
1227
        self.bulletText = bulletText
 
1228
        self.style1 = style # make sure Flowable doesn't use this unless wanted! call it style1 NOT style
 
1229
        #self.spaceBefore = self.spaceAfter = 0
 
1230
        self.program = [] # program before layout
 
1231
        self.formattedProgram = [] # after layout
 
1232
        self.remainder = None # follow on paragraph if any
 
1233
        self.state = state # initial formatting state (for completions)
 
1234
        if not state:
 
1235
            self.spaceBefore = style.spaceBefore
 
1236
            self.spaceAfter = style.spaceAfter
 
1237
            #self.spaceBefore = "invalid value"
 
1238
        #if hasattr(self, "spaceBefore") and debug:
 
1239
        #    print "spaceBefore is", self.spaceBefore, self.parsedText
 
1240
        self.bold = 0
 
1241
        self.italic = 0
 
1242
        self.face = style.fontName
 
1243
        self.size = style.fontSize
 
1244
 
 
1245
    def getSpaceBefore(self):
 
1246
        #if debug:
 
1247
        #    print "got space before", self.spaceBefore
 
1248
        return self.spaceBefore
 
1249
 
 
1250
    def getSpaceAfter(self):
 
1251
        #print "got space after", self.spaceAfter
 
1252
        return self.spaceAfter
 
1253
 
 
1254
    def wrap(self, availableWidth, availableHeight):
 
1255
        if debug:
 
1256
            print "WRAPPING", id(self), availableWidth, availableHeight
 
1257
            print "   ", self.formattedProgram
 
1258
            print "   ", self.program
 
1259
        self.availableHeight = availableHeight
 
1260
        self.myengine = p = paragraphEngine()
 
1261
        p.baseindent = self.baseindent # for shifting bullets as needed
 
1262
        parsedText = self.parsedText
 
1263
        formattedProgram = self.formattedProgram
 
1264
        state = self.state
 
1265
        if state:
 
1266
            leading = state["leading"]
 
1267
        else:
 
1268
            leading = self.style1.leading
 
1269
        program = self.program
 
1270
        self.cansplit = 1 # until proven otherwise
 
1271
        if state:
 
1272
            p.resetState(state)
 
1273
            p.x = 0
 
1274
            p.y = 0
 
1275
            needatleast = state["leading"]
 
1276
        else:
 
1277
            needatleast = self.style1.leading
 
1278
        if availableHeight<=needatleast:
 
1279
            self.cansplit = 0
 
1280
            #if debug:
 
1281
            #    print "CANNOT COMPILE, NEED AT LEAST", needatleast, 'AVAILABLE', availableHeight
 
1282
            return (availableHeight+1, availableWidth) # cannot split
 
1283
        if parsedText is None and program is None:
 
1284
            raise ValueError, "need parsedText for formatting"
 
1285
        if not program:
 
1286
            self.program = program = self.compileProgram(parsedText)
 
1287
        if not self.formattedProgram:
 
1288
            (formattedProgram, remainder, \
 
1289
             laststate, heightused) = p.format(availableWidth, availableHeight, program, leading)
 
1290
            self.formattedProgram = formattedProgram
 
1291
            self.height = heightused
 
1292
            self.laststate = laststate
 
1293
            self.remainderProgram = remainder
 
1294
        else:
 
1295
            heightused = self.height
 
1296
            remainder = None
 
1297
        # too big if there is a remainder
 
1298
        if remainder:
 
1299
            # lie about the height: it must be split anyway
 
1300
            #if debug:
 
1301
            #    print "I need to split", self.formattedProgram
 
1302
            #    print "heightused", heightused, "available", availableHeight, "remainder", len(remainder)
 
1303
            height = availableHeight + 1
 
1304
            #print "laststate is", laststate
 
1305
            #print "saving remainder", remainder
 
1306
            self.remainder = Para(self.style1, parsedText=None, bulletText=None, \
 
1307
                                  state=laststate, context=self.context)
 
1308
            self.remainder.program = remainder
 
1309
            self.remainder.spaceAfter = self.spaceAfter
 
1310
            self.spaceAfter = 0
 
1311
        else:
 
1312
            self.remainder = None # no extra
 
1313
            height = heightused
 
1314
            if height>availableHeight:
 
1315
                height = availableHeight-0.1
 
1316
            #if debug:
 
1317
            #    print "giving height", height, "of", availableHeight, self.parsedText
 
1318
        result = (availableWidth, height)
 
1319
        if debug:
 
1320
            (w, h) = result
 
1321
            if abs(availableHeight-h)<0.2:
 
1322
                print "exact match???" + repr(availableHeight, h)
 
1323
            print "wrap is", (availableWidth, availableHeight), result
 
1324
        return result
 
1325
 
 
1326
    def split(self, availableWidth, availableHeight):
 
1327
        #if debug:
 
1328
        #    print "SPLITTING", id(self), availableWidth, availableHeight
 
1329
        if availableHeight<=0 or not self.cansplit:
 
1330
            #if debug:
 
1331
            #    print "cannot split", availableWidth, "too small"
 
1332
            return [] # wrap failed to find a split
 
1333
        self.availableHeight = availableHeight
 
1334
        formattedProgram = self.formattedProgram
 
1335
        #print "formattedProgram is", formattedProgram
 
1336
        if formattedProgram is None:
 
1337
            raise ValueError, "must call wrap before split"
 
1338
        elif not formattedProgram:
 
1339
            # no first line in self: fail to split
 
1340
            return []
 
1341
        remainder = self.remainder
 
1342
        if remainder:
 
1343
            #print "SPLITTING"
 
1344
            result= [self, remainder]
 
1345
        else:
 
1346
            result= [self]
 
1347
        #if debug: print "split is", result
 
1348
        return result
 
1349
 
 
1350
    def draw(self):
 
1351
        p = self.myengine #paragraphEngine()
 
1352
        formattedProgram = self.formattedProgram
 
1353
        if formattedProgram is None:
 
1354
            raise ValueError, "must call wrap before draw"
 
1355
        state = self.state
 
1356
        laststate = self.laststate
 
1357
        if state:
 
1358
            p.resetState(state)
 
1359
            p.x = 0
 
1360
            p.y = 0
 
1361
        c = self.canv
 
1362
        #if debug:
 
1363
        #    print id(self), "page number", c.getPageNumber()
 
1364
        height = self.height
 
1365
        if state:
 
1366
            leading = state["leading"]
 
1367
        else:
 
1368
            leading = self.style1.leading
 
1369
        #if debug:
 
1370
        #    c.rect(0,0,-1, height-self.size, fill=1, stroke=1)
 
1371
        c.translate(0, height-self.size)
 
1372
        t = c.beginText()
 
1373
        #t.setTextOrigin(0,0)
 
1374
        if DUMPPROGRAM or debug:
 
1375
            print "="*44, "now running program"
 
1376
            for x in formattedProgram:
 
1377
                print x
 
1378
            print "-"*44
 
1379
        laststate = p.runOpCodes(formattedProgram, c, t)
 
1380
        #print laststate["x"], laststate["y"]
 
1381
        c.drawText(t)
 
1382
 
 
1383
    def compileProgram(self, parsedText, program=None):
 
1384
        style = self.style1
 
1385
        # standard parameters
 
1386
        #program = self.program
 
1387
        if program is None:
 
1388
            program = []
 
1389
        a = program.append
 
1390
        fn = style.fontName
 
1391
        # add style information if there was no initial state
 
1392
        a( ("face", fn ) )
 
1393
        from reportlab.lib.fonts import ps2tt
 
1394
        (self.face, self.bold, self.italic) = ps2tt(fn)
 
1395
        a( ("size", style.fontSize ) )
 
1396
        self.size = style.fontSize
 
1397
        a( ("align", style.alignment ) )
 
1398
        a( ("indent", style.leftIndent ) )
 
1399
        if style.firstLineIndent:
 
1400
            a( ("indent", style.firstLineIndent ) ) # must be undone later
 
1401
        a( ("rightIndent", style.rightIndent ) )
 
1402
        a( ("leading", style.leading) )
 
1403
        if style.textColor:
 
1404
            a( ("color", style.textColor) )
 
1405
        #a( ("nextLine", 0) ) # clear for next line
 
1406
        if self.bulletText:
 
1407
            self.do_bullet(self.bulletText, program)
 
1408
        self.compileComponent(parsedText, program)
 
1409
        # now look for a place where to insert the unindent after the first line
 
1410
        if style.firstLineIndent:
 
1411
            count = 0
 
1412
            for x in program:
 
1413
                count = count+1
 
1414
                tx = type(x)
 
1415
                if tx in (StringType, UnicodeType, InstanceType):
 
1416
                    break
 
1417
            program.insert( count, ("indent", -style.firstLineIndent ) ) # defaults to end if no visibles
 
1418
        #print "="*8, id(self), "program is"
 
1419
        #for x in program:
 
1420
        #    print x
 
1421
##        print "="*11
 
1422
##        # check pushes and pops
 
1423
##        stackcount = 0
 
1424
##        dump = 0
 
1425
##        for x in program:
 
1426
##            if dump:
 
1427
##                print "dump:", x
 
1428
##            if type(x) is TupleType:
 
1429
##                i = x[0]
 
1430
##                if i=="push":
 
1431
##                    stackcount = stackcount+1
 
1432
##                    print " "*stackcount, "push", stackcount
 
1433
##                if i=="pop":
 
1434
##                    stackcount = stackcount-1
 
1435
##                    print " "*stackcount, "pop", stackcount
 
1436
##                if stackcount<0:
 
1437
##                    dump=1
 
1438
##                    print "STACK UNDERFLOW!"
 
1439
##        if dump: stop
 
1440
        return program
 
1441
 
 
1442
    def linearize(self, program = None, parsedText=None):
 
1443
        #print "LINEARIZING", self
 
1444
        #program = self.program = []
 
1445
        if parsedText is None:
 
1446
            parsedText = self.parsedText
 
1447
        style = self.style1
 
1448
        if program is None:
 
1449
            program = []
 
1450
        program.append( ("push",) )
 
1451
        if style.spaceBefore:
 
1452
            program.append( ("leading", style.spaceBefore+style.leading) )
 
1453
        else:
 
1454
            program.append( ("leading", style.leading) )
 
1455
        program.append( ("nextLine", 0) )
 
1456
        program = self.compileProgram(parsedText, program=program)
 
1457
        program.append( ("pop",) )
 
1458
        # go to old margin
 
1459
        program.append( ("push",) )
 
1460
        if style.spaceAfter:
 
1461
            program.append( ("leading", style.spaceAfter) )
 
1462
        else:
 
1463
            program.append( ("leading", 0) )
 
1464
        program.append( ("nextLine", 0) )
 
1465
        program.append( ("pop",) )
 
1466
 
 
1467
    def compileComponent(self, parsedText, program):
 
1468
        import types
 
1469
        ttext = type(parsedText)
 
1470
        #program = self.program
 
1471
        if ttext in (StringType, UnicodeType):
 
1472
            # handle special characters here...
 
1473
            # short cut
 
1474
            if parsedText:
 
1475
                stext = parsedText.strip()
 
1476
                if not stext:
 
1477
                    program.append(" ") # contract whitespace to single space
 
1478
                else:
 
1479
                    handleSpecialCharacters(self, parsedText, program)
 
1480
        elif ttext is ListType:
 
1481
            for e in parsedText:
 
1482
                self.compileComponent(e, program)
 
1483
        elif ttext is TupleType:
 
1484
            (tagname, attdict, content, extra) = parsedText
 
1485
            if not attdict:
 
1486
                attdict = {}
 
1487
            compilername = "compile_"+tagname
 
1488
            compiler = getattr(self, compilername, None)
 
1489
            if compiler is not None:
 
1490
                compiler(attdict, content, extra, program)
 
1491
            else:
 
1492
                # just pass the tag through
 
1493
                if debug:
 
1494
                    L = [ "<" + tagname ]
 
1495
                    a = L.append
 
1496
                    if not attdict: attdict = {}
 
1497
                    for (k, v) in attdict.items():
 
1498
                        a(" %s=%s" % (k,v))
 
1499
                    if content:
 
1500
                        a(">")
 
1501
                        a(str(content))
 
1502
                        a("</%s>" % tagname)
 
1503
                    else:
 
1504
                        a("/>")
 
1505
                    t = ''.join(L)
 
1506
                    handleSpecialCharacters(self, t, program)
 
1507
                else:
 
1508
                    raise ValueError, "don't know how to handle tag " + repr(tagname)
 
1509
 
 
1510
    def shiftfont(self, program, face=None, bold=None, italic=None):
 
1511
        oldface = self.face
 
1512
        oldbold = self.bold
 
1513
        olditalic = self.italic
 
1514
        oldfontinfo = (oldface, oldbold, olditalic)
 
1515
        if face is None: face = oldface
 
1516
        if bold is None: bold = oldbold
 
1517
        if italic is None: italic = olditalic
 
1518
        self.face = face
 
1519
        self.bold = bold
 
1520
        self.italic = italic
 
1521
        from reportlab.lib.fonts import tt2ps
 
1522
        font = tt2ps(face,bold,italic)
 
1523
        oldfont = tt2ps(oldface,oldbold,olditalic)
 
1524
        if font!=oldfont:
 
1525
            program.append( ("face", font ) )
 
1526
        return oldfontinfo
 
1527
 
 
1528
    def compile_(self, attdict, content, extra, program):
 
1529
        # "anonymous" tag: just do the content
 
1530
        for e in content:
 
1531
            self.compileComponent(e, program)
 
1532
    #compile_para = compile_ # at least for now...
 
1533
 
 
1534
    def compile_pageNumber(self, attdict, content, extra, program):
 
1535
        program.append(PageNumberObject())
 
1536
 
 
1537
    def compile_b(self, attdict, content, extra, program):
 
1538
        (f,b,i) = self.shiftfont(program, bold=1)
 
1539
        for e in content:
 
1540
            self.compileComponent(e, program)
 
1541
        self.shiftfont(program, bold=b)
 
1542
 
 
1543
    def compile_i(self, attdict, content, extra, program):
 
1544
        (f,b,i) = self.shiftfont(program, italic=1)
 
1545
        for e in content:
 
1546
            self.compileComponent(e, program)
 
1547
        self.shiftfont(program, italic=i)
 
1548
 
 
1549
    def compile_u(self, attdict, content, extra, program):
 
1550
        # XXXX must eventually add things like alternative colors
 
1551
        #program = self.program
 
1552
        program.append( ('lineOperation', UNDERLINE) )
 
1553
        for e in content:
 
1554
            self.compileComponent(e, program)
 
1555
        program.append( ('endLineOperation', UNDERLINE) )
 
1556
 
 
1557
    def compile_sub(self, attdict, content, extra, program):
 
1558
        size = self.size
 
1559
        self.size = newsize = size * 0.7
 
1560
        rise = size*0.5
 
1561
        #program = self.program
 
1562
        program.append( ('size', newsize) )
 
1563
        self.size = size
 
1564
        program.append( ('rise', -rise) )
 
1565
        for e in content:
 
1566
            self.compileComponent(e, program)
 
1567
        program.append( ('size', size) )
 
1568
        program.append( ('rise', rise) )
 
1569
 
 
1570
    def compile_ul(self, attdict, content, extra, program, tagname="ul"):
 
1571
        # by transformation
 
1572
        #print "compile", tagname, attdict
 
1573
        atts = attdict.copy()
 
1574
        bulletmaker = bulletMaker(tagname, atts, self.context)
 
1575
        # now do each element as a separate paragraph
 
1576
        for e in content:
 
1577
            te = type(e)
 
1578
            if te in (StringType, UnicodeType):
 
1579
                if e.strip():
 
1580
                    raise ValueError, "don't expect CDATA between list elements"
 
1581
            elif te is TupleType:
 
1582
                (tagname, attdict1, content1, extra) = e
 
1583
                if tagname!="li":
 
1584
                    raise ValueError, "don't expect %s inside list" % repr(tagname)
 
1585
                newatts = atts.copy()
 
1586
                if attdict1:
 
1587
                    newatts.update(attdict1)
 
1588
                bulletmaker.makeBullet(newatts)
 
1589
                self.compile_para(newatts, content1, extra, program)
 
1590
 
 
1591
    def compile_ol(self, attdict, content, extra, program):
 
1592
        return self.compile_ul(attdict, content, extra, program, tagname="ol")
 
1593
 
 
1594
    def compile_dl(self, attdict, content, extra, program):
 
1595
        # by transformation
 
1596
        #print "compile", tagname, attdict
 
1597
        atts = attdict.copy()
 
1598
        # by transformation
 
1599
        #print "compile", tagname, attdict
 
1600
        atts = attdict.copy()
 
1601
        bulletmaker = bulletMaker("dl", atts, self.context)
 
1602
        # now do each element as a separate paragraph
 
1603
        contentcopy = list(content) # copy for destruction
 
1604
        bullet = ""
 
1605
        while contentcopy:
 
1606
            e = contentcopy[0]
 
1607
            del contentcopy[0]
 
1608
            te = type(e)
 
1609
            if te in (StringType, UnicodeType):
 
1610
                if e.strip():
 
1611
                    raise ValueError, "don't expect CDATA between list elements"
 
1612
                elif not contentcopy:
 
1613
                    break # done at ending whitespace
 
1614
                else:
 
1615
                    continue # ignore intermediate whitespace
 
1616
            elif te is TupleType:
 
1617
                (tagname, attdict1, content1, extra) = e
 
1618
                if tagname!="dd" and tagname!="dt":
 
1619
                    raise ValueError, "don't expect %s here inside list, expect 'dd' or 'dt'" % \
 
1620
                          repr(tagname)
 
1621
                if tagname=="dt":
 
1622
                    if bullet:
 
1623
                        raise ValueError, "dt will not be displayed unless followed by a dd: "+repr(bullet)
 
1624
                    if content1:
 
1625
                        self.compile_para(attdict1, content1, extra, program)
 
1626
                        # raise ValueError, \
 
1627
                        # "only simple strings supported in dd content currently: "+repr(content1)
 
1628
                elif tagname=="dd":
 
1629
                    newatts = atts.copy()
 
1630
                    if attdict1:
 
1631
                        newatts.update(attdict1)
 
1632
                    bulletmaker.makeBullet(newatts, bl=bullet)
 
1633
                    self.compile_para(newatts, content1, extra, program)
 
1634
                    bullet = "" # don't use this bullet again
 
1635
        if bullet:
 
1636
            raise ValueError, "dt will not be displayed unless followed by a dd"+repr(bullet)
 
1637
 
 
1638
    def compile_super(self, attdict, content, extra, program):
 
1639
        size = self.size
 
1640
        self.size = newsize = size * 0.7
 
1641
        rise = size*0.5
 
1642
        #program = self.program
 
1643
        program.append( ('size', newsize) )
 
1644
        program.append( ('rise', rise) )
 
1645
        for e in content:
 
1646
            self.compileComponent(e, program)
 
1647
        program.append( ('size', size) )
 
1648
        self.size = size
 
1649
        program.append( ('rise', -rise) )
 
1650
 
 
1651
    def compile_font(self, attdict, content, extra, program):
 
1652
        #program = self.program
 
1653
        program.append( ("push",) ) # store current data
 
1654
        if attdict.has_key("face"):
 
1655
            face = attdict["face"]
 
1656
            from reportlab.lib.fonts import tt2ps
 
1657
            try:
 
1658
                font = tt2ps(face,self.bold,self.italic)
 
1659
            except:
 
1660
                font = face # better work!
 
1661
            program.append( ("face", font ) )
 
1662
        if attdict.has_key("color"):
 
1663
            colorname = attdict["color"]
 
1664
            program.append( ("color", colorname) )
 
1665
        if attdict.has_key("size"):
 
1666
            #size = float(attdict["size"]) # really should convert int, cm etc here!
 
1667
            size = attdict["size"]
 
1668
            program.append( ("size", size) )
 
1669
        for e in content:
 
1670
            self.compileComponent(e, program)
 
1671
        program.append( ("pop",) ) # restore as before
 
1672
 
 
1673
    def compile_a(self, attdict, content, extra, program):
 
1674
        url = attdict["href"]
 
1675
        colorname = attdict.get("color", "blue")
 
1676
        #program = self.program
 
1677
        Link = HotLink(url)
 
1678
        program.append( ("push",) ) # store current data
 
1679
        program.append( ("color", colorname) )
 
1680
        program.append( ('lineOperation', Link) )
 
1681
        program.append( ('lineOperation', UNDERLINE) )
 
1682
        for e in content:
 
1683
            self.compileComponent(e, program)
 
1684
        program.append( ('endLineOperation', UNDERLINE) )
 
1685
        program.append( ('endLineOperation', Link) )
 
1686
        program.append( ("pop",) ) # restore as before
 
1687
 
 
1688
    def compile_link(self, attdict, content, extra, program):
 
1689
        dest = attdict["destination"]
 
1690
        colorname = attdict.get("color", None)
 
1691
        #program = self.program
 
1692
        Link = InternalLink(dest)
 
1693
        program.append( ("push",) ) # store current data
 
1694
        if colorname:
 
1695
            program.append( ("color", colorname) )
 
1696
        program.append( ('lineOperation', Link) )
 
1697
        program.append( ('lineOperation', UNDERLINE) )
 
1698
        for e in content:
 
1699
            self.compileComponent(e, program)
 
1700
        program.append( ('endLineOperation', UNDERLINE) )
 
1701
        program.append( ('endLineOperation', Link) )
 
1702
        program.append( ("pop",) ) # restore as before
 
1703
 
 
1704
    def compile_setLink(self, attdict, content, extra, program):
 
1705
        dest = attdict["destination"]
 
1706
        colorname = attdict.get("color", "blue")
 
1707
        #program = self.program
 
1708
        Link = DefDestination(dest)
 
1709
        program.append( ("push",) ) # store current data
 
1710
        if colorname:
 
1711
            program.append( ("color", colorname) )
 
1712
        program.append( ('lineOperation', Link) )
 
1713
        if colorname:
 
1714
            program.append( ('lineOperation', UNDERLINE) )
 
1715
        for e in content:
 
1716
            self.compileComponent(e, program)
 
1717
        if colorname:
 
1718
            program.append( ('endLineOperation', UNDERLINE) )
 
1719
        program.append( ('endLineOperation', Link) )
 
1720
        program.append( ("pop",) ) # restore as before
 
1721
 
 
1722
    #def compile_p(self, attdict, content, extra, program):
 
1723
    #    # have to be careful about base indent here!
 
1724
    #    not finished
 
1725
 
 
1726
    def compile_bullet(self, attdict, content, extra, program):
 
1727
        ### eventually should allow things like images and graphics in bullets too XXXX
 
1728
        if len(content)!=1 or type(content[0]) not in (StringType, UnicodeType):
 
1729
            raise ValueError, "content for bullet must be a single string"
 
1730
        text = content[0]
 
1731
        self.do_bullet(text, program)
 
1732
 
 
1733
    def do_bullet(self, text, program):
 
1734
        style = self.style1
 
1735
        #program = self.program
 
1736
        indent = style.bulletIndent + self.baseindent
 
1737
        font = style.bulletFontName
 
1738
        size = style.bulletFontSize
 
1739
        program.append( ("bullet", text, indent, font, size) )
 
1740
 
 
1741
    def compile_tt(self, attdict, content, extra, program):
 
1742
        (f,b,i) = self.shiftfont(program, face="Courier")
 
1743
        for e in content:
 
1744
            self.compileComponent(e, program)
 
1745
        self.shiftfont(program, face=f)
 
1746
 
 
1747
    def compile_greek(self, attdict, content, extra, program):
 
1748
        self.compile_font({"face": "symbol"}, content, extra, program)
 
1749
 
 
1750
    def compile_evalString(self, attdict, content, extra, program):
 
1751
        program.append( EvalStringObject(attdict, content, extra, self.context) )
 
1752
 
 
1753
    def compile_name(self, attdict, content, extra, program):
 
1754
        program.append( NameObject(attdict, content, extra, self.context) )
 
1755
 
 
1756
    def compile_getName(self, attdict, content, extra, program):
 
1757
        program.append( GetNameObject(attdict, content, extra, self.context) )
 
1758
 
 
1759
    def compile_seq(self, attdict, content, extra, program):
 
1760
        program.append( SeqObject(attdict, content, extra, self.context) )
 
1761
 
 
1762
    def compile_seqReset(self, attdict, content, extra, program):
 
1763
        program.append( SeqResetObject(attdict, content, extra, self.context) )
 
1764
 
 
1765
    def compile_seqDefault(self, attdict, content, extra, program):
 
1766
        program.append( SeqDefaultObject(attdict, content, extra, self.context) )
 
1767
 
 
1768
    def compile_para(self, attdict, content, extra, program, stylename = "para.defaultStyle"):
 
1769
        if attdict is None:
 
1770
            attdict = {}
 
1771
        context = self.context
 
1772
        stylename = attdict.get("style", stylename)
 
1773
        style = context[stylename]
 
1774
        newstyle = SimpleStyle(name="rml2pdf internal embedded style", parent=style)
 
1775
        newstyle.addAttributes(attdict)
 
1776
        bulletText = attdict.get("bulletText", None)
 
1777
        mystyle = self.style1
 
1778
        thepara = Para(newstyle, content, context=context, bulletText=bulletText)
 
1779
        # possible ref loop on context, break later
 
1780
        # now compile it and add it to the program
 
1781
        mybaseindent = self.baseindent
 
1782
        self.baseindent = thepara.baseindent = mystyle.leftIndent + self.baseindent
 
1783
        thepara.linearize(program=program)
 
1784
        program.append( ("nextLine", 0) )
 
1785
        self.baseindent = mybaseindent
 
1786
 
 
1787
class bulletMaker:
 
1788
    def __init__(self, tagname, atts, context):
 
1789
        self.tagname = tagname
 
1790
        #print "context is", context
 
1791
        style = "li.defaultStyle"
 
1792
        self.style = style = atts.get("style", style)
 
1793
        typ = {"ul": "disc", "ol": "1", "dl": None}[tagname]
 
1794
        #print tagname, "bulletmaker type is", typ
 
1795
        self.typ =typ = atts.get("type", typ)
 
1796
        #print tagname, "bulletmaker type is", typ
 
1797
        if not atts.has_key("leftIndent"):
 
1798
            # get the style so you can choose an indent length
 
1799
            thestyle = context[style]
 
1800
            from reportlab.pdfbase.pdfmetrics import stringWidth
 
1801
            size = thestyle.fontSize
 
1802
            indent = stringWidth("XXX", "Courier", size)
 
1803
            atts["leftIndent"] = str(indent)
 
1804
        self.count = 0
 
1805
 
 
1806
    def makeBullet(self, atts, bl=None):
 
1807
        count = self.count = self.count+1
 
1808
        typ = self.typ
 
1809
        tagname = self.tagname
 
1810
        #print "makeBullet", tagname, typ, count
 
1811
        # forget space before for non-first elements
 
1812
        if count>1:
 
1813
            atts["spaceBefore"] = "0"
 
1814
        if bl is None:
 
1815
            if tagname=="ul":
 
1816
                if typ=="disc": bl = chr(109)
 
1817
                elif typ=="circle": bl = chr(108)
 
1818
                elif typ=="square": bl = chr(110)
 
1819
                else:
 
1820
                    raise ValueError, "unordered list type %s not implemented" % repr(typ)
 
1821
                if not atts.has_key("bulletFontName"):
 
1822
                    atts["bulletFontName"] = "ZapfDingbats"
 
1823
            elif tagname=="ol":
 
1824
                if typ=="1": bl = repr(count)
 
1825
                elif typ=="a":
 
1826
                    theord = ord("a")+count-1
 
1827
                    bl = chr(theord)
 
1828
                elif typ=="A":
 
1829
                    theord = ord("A")+count-1
 
1830
                    bl = chr(theord)
 
1831
                else:
 
1832
                    raise ValueError, "ordered bullet type %s not implemented" % repr(typ)
 
1833
            else:
 
1834
                raise ValueError, "bad tagname "+repr(tagname)
 
1835
        if not atts.has_key("bulletText"):
 
1836
            atts["bulletText"] = bl
 
1837
        if not atts.has_key("style"):
 
1838
            atts["style"] = self.style
 
1839
 
 
1840
class EvalStringObject:
 
1841
    "this will only work if rml2pdf is present"
 
1842
 
 
1843
    tagname = "evalString"
 
1844
 
 
1845
    def __init__(self, attdict, content, extra, context):
 
1846
        if not attdict:
 
1847
            attdict = {}
 
1848
        self.attdict = attdict
 
1849
        self.content = content
 
1850
        self.context = context
 
1851
        self.extra = extra
 
1852
 
 
1853
    def getOp(self, tuple, engine):
 
1854
        from rlextra.rml2pdf.rml2pdf import Controller
 
1855
        #print "tuple", tuple
 
1856
        op = self.op = Controller.processTuple(tuple, self.context, {})
 
1857
        return op
 
1858
 
 
1859
    def width(self, engine):
 
1860
        from reportlab.pdfbase.pdfmetrics import stringWidth
 
1861
        content = self.content
 
1862
        if not content:
 
1863
            content = []
 
1864
        tuple = (self.tagname, self.attdict, content, self.extra)
 
1865
        op = self.op = self.getOp(tuple, engine)
 
1866
        #print op.__class__
 
1867
        #print op.pcontent
 
1868
        #print self
 
1869
        s = str(op)
 
1870
        return stringWidth(s, engine.fontName, engine.fontSize)
 
1871
 
 
1872
    def execute(self, engine, textobject, canvas):
 
1873
        textobject.textOut(str(self.op))
 
1874
 
 
1875
class SeqObject(EvalStringObject):
 
1876
 
 
1877
    def getOp(self, tuple, engine):
 
1878
        from reportlab.lib.sequencer import getSequencer
 
1879
        globalsequencer = getSequencer()
 
1880
        attr = self.attdict
 
1881
        #if it has a template, use that; otherwise try for id;
 
1882
        #otherwise take default sequence
 
1883
        if attr.has_key('template'):
 
1884
            templ = attr['template']
 
1885
            op = self.op = templ % globalsequencer
 
1886
            return op
 
1887
        elif attr.has_key('id'):
 
1888
            id = attr['id']
 
1889
        else:
 
1890
            id = None
 
1891
        op = self.op = globalsequencer.nextf(id)
 
1892
        return op
 
1893
 
 
1894
class NameObject(EvalStringObject):
 
1895
    tagname = "name"
 
1896
    def execute(self, engine, textobject, canvas):
 
1897
        pass # name doesn't produce any output
 
1898
 
 
1899
class SeqDefaultObject(NameObject):
 
1900
 
 
1901
    def getOp(self, tuple, engine):
 
1902
        from reportlab.lib.sequencer import getSequencer
 
1903
        globalsequencer = getSequencer()
 
1904
        attr = self.attdict
 
1905
        try:
 
1906
            default = attr['id']
 
1907
        except KeyError:
 
1908
            default = None
 
1909
        globalsequencer.setDefaultCounter(default)
 
1910
        self.op = ""
 
1911
        return ""
 
1912
 
 
1913
class SeqResetObject(NameObject):
 
1914
 
 
1915
    def getOp(self, tuple, engine):
 
1916
        from reportlab.lib.sequencer import getSequencer
 
1917
        import math
 
1918
        globalsequencer = getSequencer()
 
1919
        attr = self.attdict
 
1920
        try:
 
1921
            id = attr['id']
 
1922
        except KeyError:
 
1923
            id = None
 
1924
        try:
 
1925
            base = math.atoi(attr['base'])
 
1926
        except:
 
1927
            base=0
 
1928
        globalsequencer.reset(id, base)
 
1929
        self.op = ""
 
1930
        return ""
 
1931
 
 
1932
class GetNameObject(EvalStringObject):
 
1933
    tagname = "getName"
 
1934
 
 
1935
class PageNumberObject:
 
1936
 
 
1937
    def __init__(self, example="XXX"):
 
1938
        self.example = example # XXX SHOULD ADD THE ABILITY TO PASS IN EXAMPLES
 
1939
 
 
1940
    def width(self, engine):
 
1941
        from reportlab.pdfbase.pdfmetrics import stringWidth
 
1942
        return stringWidth(self.example, engine.fontName, engine.fontSize)
 
1943
 
 
1944
    def execute(self, engine, textobject, canvas):
 
1945
        n = canvas.getPageNumber()
 
1946
        textobject.textOut(str(n))
 
1947
 
 
1948
### this should be moved into rml2pdf
 
1949
def EmbedInRml2pdf():
 
1950
    "make the para the default para implementation in rml2pdf"
 
1951
    from rlextra.rml2pdf.rml2pdf import MapNode, Controller # may not need to use superclass?
 
1952
    global paraMapper, theParaMapper, ulMapper
 
1953
 
 
1954
    class paraMapper(MapNode):
 
1955
        #stylename = "para.defaultStyle"
 
1956
        def translate(self, nodetuple, controller, context, overrides):
 
1957
            (tagname, attdict, content, extra) = nodetuple
 
1958
            stylename = tagname+".defaultStyle"
 
1959
            stylename = attdict.get("style", stylename)
 
1960
            style = context[stylename]
 
1961
            mystyle = SimpleStyle(name="rml2pdf internal style", parent=style)
 
1962
            mystyle.addAttributes(attdict)
 
1963
            bulletText = attdict.get("bulletText", None)
 
1964
            # can we use the fast implementation?
 
1965
            import types
 
1966
            result = None
 
1967
            if not bulletText and len(content)==1:
 
1968
                text = content[0]
 
1969
                if type(text) in (StringType, UnicodeType) and "&" not in text:
 
1970
                    result = FastPara(mystyle, text)
 
1971
            if result is None:
 
1972
                result = Para(mystyle, content, context=context, bulletText=bulletText) # possible ref loop on context, break later
 
1973
            return result
 
1974
 
 
1975
    theParaMapper = paraMapper()
 
1976
 
 
1977
    class ulMapper(MapNode):
 
1978
        # wrap in a default para and let the para do it
 
1979
        def translate(self, nodetuple, controller, context, overrides):
 
1980
            thepara = ("para", {}, [nodetuple], None)
 
1981
            return theParaMapper.translate(thepara, controller, context, overrides)
 
1982
 
 
1983
    # override rml2pdf interpreters (should be moved to rml2pdf)
 
1984
    theListMapper = ulMapper()
 
1985
    Controller["ul"] = theListMapper
 
1986
    Controller["ol"] = theListMapper
 
1987
    Controller["dl"] = theListMapper
 
1988
    Controller["para"] = theParaMapper
 
1989
    Controller["h1"] = theParaMapper
 
1990
    Controller["h2"] = theParaMapper
 
1991
    Controller["h3"] = theParaMapper
 
1992
    Controller["title"] = theParaMapper
 
1993
 
 
1994
 
 
1995
testparagraph = """
 
1996
This is Text.
 
1997
<b>This is bold text.</b>
 
1998
This is Text.
 
1999
<i>This is italic text.</i>
 
2000
 
 
2001
<ul>
 
2002
    <li> this is an element at 1
 
2003
more text and even more text and on and on and so forth
 
2004
more text and even more text and on and on and so forth
 
2005
more text and even more text and on and on and so forth
 
2006
more text and even more text and on and on and so forth
 
2007
more text and even more text and on and on and so forth
 
2008
more text <tt>monospaced</tt> and back to normal
 
2009
 
 
2010
    <ul>
 
2011
        <li> this is an element at 2
 
2012
 
 
2013
more text and even more text and on and on and so forth
 
2014
more text and even more text and on and on and so forth
 
2015
 
 
2016
        <ul>
 
2017
            <li> this is an element at 3
 
2018
 
 
2019
more text and even more text and on and on and so forth
 
2020
 
 
2021
 
 
2022
                <dl bulletFontName="Helvetica-BoldOblique" spaceBefore="10" spaceAfter="10">
 
2023
                <dt>frogs</dt> <dd>Little green slimy things. Delicious with <b>garlic</b></dd>
 
2024
                <dt>kittens</dt> <dd>cute, furry, not edible</dd>
 
2025
                <dt>bunnies</dt> <dd>cute, furry,. Delicious with <b>garlic</b></dd>
 
2026
                </dl>
 
2027
 
 
2028
more text and even more text and on and on and so forth
 
2029
 
 
2030
            <ul>
 
2031
                <li> this is an element at 4
 
2032
 
 
2033
more text and even more text and on and on and so forth
 
2034
more text and even more text and on and on and so forth
 
2035
 
 
2036
                </li>
 
2037
            </ul>
 
2038
 
 
2039
more text and even more text and on and on and so forth
 
2040
more text and even more text and on and on and so forth
 
2041
 
 
2042
            </li>
 
2043
        </ul>
 
2044
more text and even more text and on and on and so forth
 
2045
more text and even more text and on and on and so forth
 
2046
 
 
2047
        </li>
 
2048
 
 
2049
    </ul>
 
2050
<u><b>UNDERLINED</b> more text and even more text and on and on and so forth
 
2051
more text and even more text and on and on and so forth</u>
 
2052
 
 
2053
<ol type="a">
 
2054
    <li>first element of the alpha list
 
2055
 
 
2056
     <ul type="square">
 
2057
        <li>first element of the square unnumberred list</li>
 
2058
 
 
2059
        <li>second element of the unnumberred list</li>
 
2060
 
 
2061
        <li>third element of the unnumberred list
 
2062
        third element of the unnumberred list
 
2063
        third element of the unnumberred list
 
2064
        third element of the unnumberred list
 
2065
        third element of the unnumberred list
 
2066
        third element of the unnumberred list
 
2067
        third element of the unnumberred list
 
2068
        </li>
 
2069
 
 
2070
        <li>fourth element of the unnumberred list</li>
 
2071
 
 
2072
      </ul>
 
2073
 
 
2074
    </li>
 
2075
 
 
2076
    <li>second element of the alpha list</li>
 
2077
 
 
2078
    <li>third element of the alpha list
 
2079
    third element of the unnumberred list &amp;#33; --> &#33;
 
2080
    third element of the unnumberred list &amp;#8704; --> &#8704;
 
2081
    third element of the unnumberred list &amp;exist; --> &exist;
 
2082
    third element of the unnumberred list
 
2083
    third element of the unnumberred list
 
2084
    third element of the unnumberred list
 
2085
    </li>
 
2086
 
 
2087
    <li>fourth element of the alpha list</li>
 
2088
 
 
2089
  </ol>
 
2090
 
 
2091
 
 
2092
    </li>
 
2093
</ul>
 
2094
 
 
2095
<a href="http://www.reportlab.com">goto www.reportlab.com</a>.
 
2096
 
 
2097
 
 
2098
<para alignment="justify">
 
2099
<font color="red" size="15">R</font>ed letter. thisisareallylongword andsoisthis andthisislonger
 
2100
justified text paragraph example
 
2101
justified text paragraph example
 
2102
justified text paragraph example
 
2103
</para>
 
2104
 
 
2105
"""
 
2106
 
 
2107
def test2(canv):
 
2108
    #print test_program; return
 
2109
    from reportlab.lib.units import inch
 
2110
    from reportlab.lib.styles import ParagraphStyle
 
2111
    from reportlab.lib import rparsexml
 
2112
    parsedpara = rparsexml.parsexmlSimple(testparagraph,entityReplacer=None)
 
2113
    S = ParagraphStyle("Normal", None)
 
2114
    P = Para(S, parsedpara)
 
2115
    (w, h) = P.wrap(5*inch, 10*inch)
 
2116
    print "wrapped as", (h,w)
 
2117
    canv.translate(1*inch, 1*inch)
 
2118
    canv.rect(0,0,5*inch,10*inch, fill=0, stroke=1)
 
2119
    P.canv = canv
 
2120
    P.draw()
 
2121
    canv.setStrokeColorRGB(1, 0, 0)
 
2122
    #canv.translate(0, 3*inch)
 
2123
    canv.rect(0,0,w,-h, fill=0, stroke=1)
 
2124
 
 
2125
def handleSpecialCharacters(engine, text, program=None):
 
2126
    from paraparser import greeks, symenc
 
2127
    from string import whitespace, atoi, atoi_error
 
2128
    standard={'lt':'<', 'gt':'>', 'amp':'&'}
 
2129
    # add space prefix if space here
 
2130
    if text[0:1] in whitespace:
 
2131
        program.append(" ")
 
2132
    #print "handling", repr(text)
 
2133
    # shortcut
 
2134
    if 0 and "&" not in text:
 
2135
        result = []
 
2136
        for x in text.split():
 
2137
            result.append(x+" ")
 
2138
        if result:
 
2139
            last = result[-1]
 
2140
            if text[-1:] not in whitespace:
 
2141
                result[-1] = last.strip()
 
2142
        program.extend(result)
 
2143
        return program
 
2144
    if program is None:
 
2145
        program = []
 
2146
    amptext = text.split("&")
 
2147
    first = 1
 
2148
    lastfrag = amptext[-1]
 
2149
    for fragment in amptext:
 
2150
        if not first:
 
2151
            # check for special chars
 
2152
            semi = fragment.find(";")
 
2153
            if semi>0:
 
2154
                name = fragment[:semi]
 
2155
                if name[0]=='#':
 
2156
                    try:
 
2157
                        if name[1] == 'x':
 
2158
                            n = atoi(name[2:], 16)
 
2159
                        else:
 
2160
                            n = atoi(name[1:])
 
2161
                    except atoi_error:
 
2162
                        n = -1
 
2163
                    if 0<=n<=255: fragment = chr(n)+fragment[semi+1:]
 
2164
                    elif symenc.has_key(n):
 
2165
                        fragment = fragment[semi+1:]
 
2166
                        (f,b,i) = engine.shiftfont(program, face="symbol")
 
2167
                        program.append(symenc[n])
 
2168
                        engine.shiftfont(program, face=f)
 
2169
                        if fragment and fragment[0] in whitespace:
 
2170
                            program.append(" ") # follow with a space
 
2171
                    else:
 
2172
                        fragment = "&"+fragment
 
2173
                elif standard.has_key(name):
 
2174
                    fragment = standard[name]+fragment[semi+1:]
 
2175
                elif greeks.has_key(name):
 
2176
                    fragment = fragment[semi+1:]
 
2177
                    greeksub = greeks[name]
 
2178
                    (f,b,i) = engine.shiftfont(program, face="symbol")
 
2179
                    program.append(greeksub)
 
2180
                    engine.shiftfont(program, face=f)
 
2181
                    if fragment and fragment[0] in whitespace:
 
2182
                        program.append(" ") # follow with a space
 
2183
                else:
 
2184
                    # add back the &
 
2185
                    fragment = "&"+fragment
 
2186
            else:
 
2187
                # add back the &
 
2188
                fragment = "&"+fragment
 
2189
        # add white separated components of fragment followed by space
 
2190
        sfragment = fragment.split()
 
2191
        for w in sfragment[:-1]:
 
2192
            program.append(w+" ")
 
2193
        # does the last one need a space?
 
2194
        if sfragment and fragment:
 
2195
            # reader 3 used to go nuts if you don't special case the last frag, but it's fixed?
 
2196
            if fragment[-1] in whitespace: # or fragment==lastfrag:
 
2197
                program.append( sfragment[-1]+" " )
 
2198
            else:
 
2199
                last = sfragment[-1].strip()
 
2200
                if last:
 
2201
                    #print "last is", repr(last)
 
2202
                    program.append( last )
 
2203
        first = 0
 
2204
    #print "HANDLED", program
 
2205
    return program
 
2206
 
 
2207
def Paragraph(text, style, bulletText=None, frags=None, context=None):
 
2208
    """ Paragraph(text, style, bulletText=None)
 
2209
    intended to be like a platypus Paragraph but better.
 
2210
    """
 
2211
    # if there is no & or < in text then use the fast paragraph
 
2212
    if "&" not in text and "<" not in text:
 
2213
        return FastPara(style, simpletext=text)
 
2214
    else:
 
2215
        # use the fully featured one.
 
2216
        from reportlab.lib import rparsexml
 
2217
        parsedpara = rparsexml.parsexmlSimple(text,entityReplacer=None)
 
2218
        return Para(style, parsedText=parsedpara, bulletText=bulletText, state=None, context=context)
 
2219
 
 
2220
class UnderLineHandler:
 
2221
    def __init__(self, color=None):
 
2222
        self.color = color
 
2223
    def start_at(self, x,y, para, canvas, textobject):
 
2224
        self.xStart = x
 
2225
        self.yStart = y
 
2226
    def end_at(self, x, y, para, canvas, textobject):
 
2227
        offset = para.fontSize/8.0
 
2228
        canvas.saveState()
 
2229
        color = self.color
 
2230
        if self.color is None:
 
2231
            color = para.fontColor
 
2232
        canvas.setStrokeColor(color)
 
2233
        canvas.line(self.xStart, self.yStart-offset, x,y-offset)
 
2234
        canvas.restoreState()
 
2235
 
 
2236
UNDERLINE = UnderLineHandler()
 
2237
 
 
2238
class HotLink(UnderLineHandler):
 
2239
 
 
2240
    def __init__(self, url):
 
2241
        self.url = url
 
2242
 
 
2243
    def end_at(self, x, y, para, canvas, textobject):
 
2244
        fontsize = para.fontSize
 
2245
        rect = [self.xStart, self.yStart, x,y+fontsize]
 
2246
        if debug:
 
2247
            print "LINKING RECTANGLE", rect
 
2248
            #canvas.rect(self.xStart, self.yStart, x-self.xStart,y+fontsize-self.yStart, stroke=1)
 
2249
        self.link(rect, canvas)
 
2250
 
 
2251
    def link(self, rect, canvas):
 
2252
        canvas.linkURL(self.url, rect, relative=1)
 
2253
 
 
2254
class InternalLink(HotLink):
 
2255
 
 
2256
    def link(self, rect, canvas):
 
2257
        destinationname = self.url
 
2258
        contents = ""
 
2259
        canvas.linkRect(contents, destinationname, rect, Border="[0 0 0]")
 
2260
 
 
2261
class DefDestination(HotLink):
 
2262
 
 
2263
    defined = 0
 
2264
 
 
2265
    def link(self, rect, canvas):
 
2266
        destinationname = self.url
 
2267
        if not self.defined:
 
2268
            [x, y, x1, y1] = rect
 
2269
            canvas.bookmarkHorizontal(destinationname, x, y1) # use the upper y
 
2270
            self.defined = 1
 
2271
 
 
2272
def splitspace(text):
 
2273
    # split on spacing but include spaces at element ends
 
2274
    stext = text.split()
 
2275
    result = []
 
2276
    for e in stext:
 
2277
        result.append(e+" ")
 
2278
    return result
 
2279
 
 
2280
testlink = HotLink("http://www.reportlab.com")
 
2281
 
 
2282
test_program = [
 
2283
    ('push',),
 
2284
    ('indent', 100),
 
2285
                    ('rightIndent', 200),
 
2286
                    ('bullet', 'very long bullet', 50, 'Courier', 14),
 
2287
                    ('align', TA_CENTER),
 
2288
                    ('face', "Times-Roman"),
 
2289
                    ('size', 12),
 
2290
                    ('leading', 14),
 
2291
                    ] + splitspace("This is the first segment of the first paragraph.") + [
 
2292
                    ('lineOperation', testlink),
 
2293
                    ]+splitspace("HOTLINK This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. ") + [
 
2294
                    ('endLineOperation', testlink),
 
2295
                    ('nextLine', 0),
 
2296
                    ('align', TA_LEFT),
 
2297
                    ('bullet', 'Bullet', 10, 'Courier', 8),
 
2298
                    ('face', "Times-Roman"),
 
2299
                    ('size', 12),
 
2300
                    ('leading', 14),
 
2301
                    ] + splitspace("This is the SECOND!!! segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. ") + [
 
2302
                    ('nextLine', 0),
 
2303
                    ('align', TA_JUSTIFY),
 
2304
                    ('bullet', 'Bullet not quite as long this time', 50, 'Courier', 8),
 
2305
                    ('face', "Helvetica-Oblique"),
 
2306
                    ('size', 12),
 
2307
                    ('leading', 14),
 
2308
                    ('push',),
 
2309
                    ('color', 'red'),
 
2310
                    ] + splitspace("This is the THIRD!!! segment of the first paragraph."
 
2311
                                     ) + [
 
2312
                    ('lineOperation', UNDERLINE),
 
2313
                    ] + splitspace("This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. ") + [
 
2314
                    ('endLineOperation', UNDERLINE),
 
2315
                    ('rise', 5),
 
2316
                    "raised ", "text ",
 
2317
                    ('rise', -10),
 
2318
                    "lowered ", "text ",
 
2319
                    ('rise', 5),
 
2320
                    "normal ", "text ",
 
2321
                    ('pop',),
 
2322
                    ('indent', 100),
 
2323
                    ('rightIndent', 50),
 
2324
                    ('nextLine', 0),
 
2325
                    ('align', TA_RIGHT),
 
2326
                    ('bullet', 'O', 50, 'Courier', 14),
 
2327
                    ('face', "Helvetica"),
 
2328
                    ('size', 12),
 
2329
                    ('leading', 14),
 
2330
                    ] + splitspace("And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a ") + [
 
2331
            ('pop',),
 
2332
            ('nextLine', 0),]
 
2333
 
 
2334
 
 
2335
def test():
 
2336
    from pprint import pprint
 
2337
    #print test_program; return
 
2338
    from reportlab.pdfgen import canvas
 
2339
    from reportlab.lib.units import inch
 
2340
    fn = "paratest0.pdf"
 
2341
    c = canvas.Canvas(fn)
 
2342
    test2(c)
 
2343
    c.showPage()
 
2344
    if 1:
 
2345
        remainder = test_program + test_program + test_program
 
2346
        laststate = {}
 
2347
        while remainder:
 
2348
            print "NEW PAGE"
 
2349
            c.translate(inch, 8*inch)
 
2350
            t = c.beginText()
 
2351
            t.setTextOrigin(0,0)
 
2352
            p = paragraphEngine()
 
2353
            p.resetState(laststate)
 
2354
            p.x = 0
 
2355
            p.y = 0
 
2356
            maxwidth = 7*inch
 
2357
            maxheight = 500
 
2358
            (formattedprogram, remainder, laststate, height) = p.format(maxwidth, maxheight, remainder)
 
2359
            if debug:
 
2360
                pprint( formattedprogram )#; return
 
2361
            laststate = p.runOpCodes(formattedprogram, c, t)
 
2362
            c.drawText(t)
 
2363
            c.showPage()
 
2364
            print "="*30, "x=", laststate["x"], "y=", laststate["y"]
 
2365
    c.save()
 
2366
    print fn
 
2367
 
 
2368
if __name__=="__main__":
 
2369
    test()