1
"""new experimental paragraph implementation
3
Intended to allow support for paragraphs in paragraphs, hotlinks,
4
embedded flowables, and underlining. The main entry point is the
7
def Paragraph(text, style, bulletText=None, frags=None)
9
Which is intended to be plug compatible with the "usual" platypus
10
paragraph except that it supports more functionality.
12
In this implementation you may embed paragraphs inside paragraphs
13
to create hierarchically organized documents.
15
This implementation adds the following paragraph-like tags (which
16
support the same attributes as paragraphs, for font specification, etc).
18
- Unnumberred lists (ala html):
25
Also <ul type="disc"> (default) or <ul type="circle">, <ul type="square">.
27
- Numberred lists (ala html):
34
Also <ul type="1"> (default) or <ul type="a">, <ul type="A">.
36
- Display lists (ala HTML):
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>
46
ALSO the following additional internal paragraph markup tags are supported
48
<u>underlined text</u>
50
<a href="http://www.reportlab.com">hyperlinked text</a>
51
<a href="http://www.reportlab.com" color="blue">hyperlinked text</a>
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>
56
<setLink destination="start" color="magenta">This is the document start
57
(define document destination inside paragraph, color is optional)</setLink>
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
66
from types import StringType, UnicodeType, InstanceType, TupleType, ListType, FloatType
68
# SET THIS TO CAUSE A VIEWING BUG WITH ACROREAD 3 (for at least one input)
77
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
79
# indent changes effect the next line
80
# align changes effect the current line
82
# need to fix spacing questions... if ends with space then space may be inserted
84
# NEGATIVE SPACE SHOULD NEVER BE EXPANDED (IN JUSTIFICATION, EG)
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
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"
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 = []
105
TEXT_STATE_VARIABLES = ("indent", "rightIndent", "fontName", "fontSize",
106
"leading", "fontColor", "lineOpHandlers", "rise",
110
def pushTextState(self):
112
for var in self.TEXT_STATE_VARIABLES:
113
val = getattr(self, var)
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
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:
131
setattr(self, var, val)
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
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
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
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, \
158
(lineIsFull, line, cursor, currentLength, \
159
usedIndent, maxLength, justStrings) = self.fitLine(remainder, maxwidth)
161
## print "failed to fit line near", cursorcount # debug
162
## for i in program1[max(0,cursorcount-10): cursorcount]:
166
## for i in program1[cursorcount:cursorcount+20]:
169
cursorcount = cursorcount+cursor # debug
170
leading = self.leading
171
if heightremaining>leading:
172
heightremaining = heightremaining-leading
175
#self.resetState(beforelinestate)
176
self.__dict__.update(beforelinestate)
177
break # no room for this line
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:]
189
# trim off the extra end of line
191
# do justification if any
192
#line = self.shrinkWrap(line
193
if alignment==TA_LEFT:
197
line = stringLine(line, currentLength)
199
line = self.shrinkWrap(line)
201
elif alignment==TA_CENTER:
203
# print "ALIGN CENTER"
205
line = stringLine(line, currentLength)
207
line = self.shrinkWrap(line)
208
line = self.centerAlign(line, currentLength, maxLength)
209
elif alignment==TA_RIGHT:
211
# print "ALIGN RIGHT"
213
line = stringLine(line, currentLength)
215
line = self.shrinkWrap(line)
216
line = self.rightAlign(line, currentLength, maxLength)
217
elif alignment==TA_JUSTIFY:
220
if remainder and lineIsFull:
222
line = simpleJustifyAlign(line, currentLength, maxLength)
224
line = self.justifyAlign(line, currentLength, maxLength)
227
line = stringLine(line, currentLength)
229
line = self.shrinkWrap(line)
231
print "no justify because line is not full or end of para"
233
raise ValueError, "bad alignment "+repr(alignment)
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)
245
return self.__dict__.copy()
247
def resetState(self, state):
249
self.__dict__.update(state)
251
## def sizeOfWord(self, word):
252
## inlineThisFunctionForEfficiency
253
## return float(stringWidth(word, self.fontName, self.fontSize))
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
267
maxcursor = len(program)
271
fontName = self.fontName
272
fontSize = self.fontSize
273
spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ")
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
282
if topcode is InstanceType:
284
width = opcode.width(self)
288
opcode = opcode.strip()
290
width = stringWidth(opcode, fontName, fontSize)
293
if saveopcode and (width or currentLength):
294
# ignore white space at margin
295
needspace = (saveopcode[-1]==" ")
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
306
# print "WORD", opcode, "wont fit, width", width, "fullwidth", fullwidth
307
# print " currentLength", currentLength, "newlength", newlength, "maxLength", maxLength
311
# fit the word: add a space then the word
313
line.append( spacewidth ) # expandable space: positive
315
line.append( opcode )
316
if abs(width)>TOOSMALLSPACE:
317
line.append( -width ) # non expanding space: negative
318
currentLength = newlength
322
elif topcode is FloatType:
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
331
if aopcode>TOOSMALLSPACE:
332
currentLength = nextLength
335
elif topcode is TupleType:
337
indicator = opcode[0]
339
if indicator=="nextLine":
340
# advance to nextLine
341
#(i, endallmarks) = opcode
343
cursor = cursor+1 # consume this element
344
terminated = done = 1
346
# print "nextLine encountered"
347
elif indicator=="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)
355
color = self.fontColor = colorname # assume its something sensible :)
357
elif indicator=="face":
359
(i, fontname) = opcode
360
fontName = self.fontName = fontname
361
spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ")
363
elif indicator=="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
373
fontSize = self.fontSize = size
375
fontSize = self.fontSize = size
376
spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ")
378
elif indicator=="leading":
379
# change font leading
380
(i, leading) = opcode
381
self.leading = leading
383
elif indicator=="indent":
384
# increase the indent
385
(i, increment) = opcode
386
indent = self.indent = self.indent + increment
388
usedIndent = max(indent, usedIndent)
389
maxLength = totalLength - usedIndent - self.rightIndent
391
elif indicator=="push":
394
elif indicator=="pop":
398
## print "stack fault near", cursor
399
## for i in program[max(0, cursor-10):cursor+10]:
404
fontName = self.fontName
405
fontSize = self.fontSize
406
spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ")
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)
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)
421
maxLength = totalLength - usedIndent - self.rightIndent
423
elif indicator=="rightIndent":
424
# increase the right indent
425
(i, increment) = opcode
426
self.rightIndent = self.rightIndent+increment
428
maxLength = totalLength - usedIndent - self.rightIndent
430
elif indicator=="rise":
432
newrise = self.rise = self.rise+rise
434
elif indicator=="align":
435
(i, alignment) = opcode
437
# print "SETTING ALIGNMENT", alignment
438
self.alignment = alignment
440
elif indicator=="lineOperation":
441
(i, handler) = opcode
443
self.lineOpHandlers = self.lineOpHandlers + [handler] # fresh copy
444
elif indicator=="endLineOperation":
445
(i, handler) = opcode
446
h = self.lineOpHandlers[:] # fresh copy
448
self.lineOpHandlers = h
452
raise ValueError, "at format time don't understand indicator "+repr(indicator)
454
raise ValueError, "op must be string, float, instance, or tuple "+repr(opcode)
460
## print "DONE FLAG IS SET"
461
## if cursor>=maxcursor:
462
## print "AT END OF PROGRAM"
464
line.append( ("nextLine", 0) )
465
#print "fitline", line
466
return (lineIsFull, line, cursor, currentLength, usedIndent, maxLength, justStrings)
468
def centerAlign(self, line, lineLength, maxLength):
469
diff = maxLength-lineLength
471
if shift>TOOSMALLSPACE:
472
return self.insertShift(line, shift)
475
def rightAlign(self, line, lineLength, maxLength):
476
shift = maxLength-lineLength
478
if shift>TOOSMALLSPACE:
479
return self.insertShift(line, shift)
482
def insertShift(self, line, shift):
483
# insert shift just before first visible element in line
488
if first and (te in (StringType, UnicodeType, InstanceType)):
494
def justifyAlign(self, line, lineLength, maxLength):
495
diff = maxLength-lineLength
496
# count EXPANDABLE SPACES AFTER THE FIRST VISIBLE
501
if te is FloatType and e>TOOSMALLSPACE and visible:
502
spacecount = spacecount+1
503
elif te in (StringType, UnicodeType, InstanceType):
505
#if debug: print "diff is", diff, "wordcount", wordcount #; die
508
shift = diff/float(spacecount)
509
if shift<=TOOSMALLSPACE:
510
#if debug: print "shift too small", shift
521
if (te in (StringType, UnicodeType, InstanceType)):
523
elif te is FloatType and e>TOOSMALLSPACE and visible:
525
result[-1] = expanded
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
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":
545
## print "adding shift before", beforething
546
## elif thingtype is FloatType:
547
## myshift = myshift + beforething
548
## del result[beforeplace]
552
## insertplace = beforeplace
553
## result.insert(insertplace, myshift)
558
def shrinkWrap(self, line):
559
# for non justified text, collapse adjacent text/shift's into single operations
563
while index<maxindex:
566
if te in (StringType, UnicodeType) and index<maxindex-1:
567
# collect strings and floats
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:
580
thefloats = thefloats + nexte
581
elif tnexte in (StringType, UnicodeType):
582
thestrings.append(nexte)
588
s = ' '.join(thestrings)
590
result.append(float(thefloats))
591
# back up for unhandled element
599
def cleanProgram(self, line):
600
"collapse adjacent spacings"
601
#return line # for debugging
605
if type(e) is FloatType:
606
# switch to expandable space if appropriate
613
if abs(last)>TOOSMALLSPACE:
619
# now go backwards and delete all floats occurring after all visible elements
620
## count = len(result)-1
622
## while count>0 and not done:
625
## if te is StringType or te is InstanceType or te is TupleType:
627
## elif te is FloatType:
630
# move end operations left and start operations left up to visibles
632
rline = range(len(result)-1)
639
next = result[nextindex]
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:
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]
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
661
#print "swap", line[index],line[nextindex]
663
result[nextindex] = this
667
def runOpCodes(self, program, canvas, textobject):
670
escape = canvas._escape
671
code = textobject._code
672
startstate = self.__dict__.copy()
675
# be sure to set them before using them (done lazily below)
676
#textobject.setFont(self.fontName, self.fontSize)
677
textobject.setFillColor(self.fontColor)
679
thislineindent = self.indent
680
thislinerightIndent = self.rightIndent
682
for opcode in program:
683
topcode = type(opcode)
684
if topcode in (StringType, UnicodeType, InstanceType):
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
695
# lazily set font (don't do it again if not needed)
696
if font!=self.fontName or 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)
706
opcode.execute(self, textobject, canvas)
707
elif topcode is FloatType:
708
# use abs value (ignore expandable marking)
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
721
newy = self.y = self.y-self.leading
722
newx = self.x = xstart
723
thislineindent = self.indent
724
thislinerightIndent = self.rightIndent
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":
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)
738
color = self.fontColor = colorname # assume its something sensible :)
740
# print color.red, color.green, color.blue
742
#print "color is", color
743
#from reportlab.lib.colors import green
744
#if color is green: print "color is green"
746
textobject.setFillColor(color)
747
elif indicator=="face":
749
(i, fontname) = opcode
750
self.fontName = fontname
751
#textobject.setFont(self.fontName, self.fontSize)
752
elif indicator=="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
762
fontSize = self.fontSize = size
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":
779
elif indicator=="pop":
780
oldcolor = self.fontColor
781
oldfont = self.fontName
782
oldsize = self.fontSize
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":
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":
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
831
self.lineOpHandlers.remove(handler)
834
#print "WARNING: HANDLER", handler, "NOT IN", newh
836
raise ValueError, "don't understand indicator "+repr(indicator)
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)
844
def stringLine(line, length):
845
"simple case: line with just strings and spacings which can be ignored"
849
if type(x) in (StringType, UnicodeType):
851
text = ' '.join(strings)
852
result = [text, float(length)]
853
nextlinemark = ("nextLine", 0)
854
if line and line[-1]==nextlinemark:
855
result.append( nextlinemark )
858
def simpleJustifyAlign(line, currentLength, maxLength):
859
"simple justification with only strings"
863
if type(x) in (StringType, UnicodeType):
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)]
872
result = [text, currentLength, ("nextLine", 0)]
873
nextlinemark = ("nextLine", 0)
874
if line and line[-1]==nextlinemark:
875
result.append( nextlinemark )
878
from reportlab.lib.colors import black
881
if text.upper() in ("Y", "YES", "TRUE", "1"):
883
elif text.upper() in ("N", "NO", "FALSE", "0"):
886
raise RMLError, "true/false attribute has illegal value '%s'" % text
888
def readAlignment(text):
894
elif up in ['CENTER', 'CENTRE']:
896
elif up == 'JUSTIFY':
899
def readLength(text):
900
"""Read a dimension measurement: accept "3in", "5cm",
901
"72 pt" and so on."""
907
numberText, units = text[:-2],text[-2:]
908
numberText = numberText.strip()
910
number = float(numberText)
912
raise ValueError, "invalid length attribute '%s'" % text
916
'cm':28.3464566929, #72/2.54; is this accurate?
921
raise RMLError, "invalid length attribute '%s'" % text
923
return number * multiplier
925
def lengthSequence(s, converter=readLength):
926
"""from "(2, 1)" or "2,1" return [2,1], for example"""
928
if s[:1]=="(" and s[-1:]==")":
931
sl = [s.strip() for s in sl]
932
sl = [converter(s) for s in sl]
937
"""Read color names or tuples, RGB or CMYK, and return a Color object."""
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)
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
953
colClass = colors.Color
955
colClass = colors.CMYKColor
956
return apply(colClass, tup)
958
class StyleAttributeConverters:
959
fontSize=[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]
973
"simplified paragraph style without all the fancy stuff"
975
fontName='Times-Roman'
984
bulletFontName='Times-Roman'
990
def __init__(self, name, parent=None, **kw):
991
mydict = self.__dict__
993
for (a,b) in parent.__dict__.items():
995
for (a,b) in kw.items():
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)
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",
1024
class FastPara(Flowable):
1025
"paragraph with no special features (not even a single ampersand!)"
1027
def __init__(self, style, simpletext):
1029
# print "FAST", id(self)
1030
if "&" in simpletext:
1031
raise ValueError, "no ampersands please!"
1033
self.simpletext = simpletext
1036
def wrap(self, availableWidth, availableHeight):
1037
simpletext = self.simpletext
1038
self.availableWidth = availableWidth
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()
1050
from reportlab.pdfbase.pdfmetrics import stringWidth
1051
spacewidth = stringWidth(" ", font, size)
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:
1065
#heightused = leading # ???
1066
while cursor<nwords and not done:
1067
thismaxlength = maxlength
1069
thismaxlength = firstmaxlength
1070
thisword = words[cursor]
1071
thiswordsize = stringWidth(thisword, font, size)
1073
thiswordsize = thiswordsize+spacewidth
1074
nextlength = currentlength + thiswordsize
1075
if not currentlength or nextlength<maxlength:
1078
currentlength = nextlength
1079
currentline.append(thisword)
1080
#print "currentline", currentline
1083
lines.append( (' '.join(currentline), currentlength, len(currentline)) )
1086
heightused = heightused+leading
1087
if heightused+leading>availableHeight:
1089
if currentlength and not done:
1090
lines.append( (' '.join(currentline), currentlength, len(currentline) ))
1091
heightused = heightused+leading
1093
self.height = heightused
1094
remainder = self.remainder = ' '.join(words[cursor:])
1095
#print "lines", lines
1096
#print "remainder is", remainder
1099
heightused = self.height
1102
result = (availableWidth, availableHeight+leading) # need to split
1104
result = (availableWidth, heightused)
1105
#if debug: print "wrap is", (availableWidth, availableHeight), result, len(lines)
1108
def split(self, availableWidth, availableHeight):
1110
leading = style.leading
1111
if availableHeight<leading:
1112
return [] # not enough space for split
1115
raise ValueError, "must wrap before split"
1116
remainder = self.remainder
1118
next = FastPara(style, remainder)
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
1136
# print "FAST", id(self), "page number", c.getPageNumber()
1137
height = self.height
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)
1146
textobject.setFillColor(style.textColor)
1149
basicWidth = self.availableWidth - rightIndent
1153
(text, length, nwords) = lines[count]
1155
thisindent = leftIndent
1157
thisindent = firstindent
1158
if alignment==TA_LEFT:
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:
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))
1172
textobject.setWordSpace(0.0)
1173
textobject.setTextOrigin(x,y)
1175
code.append('(%s) Tj' % text)
1176
#textobject.textOut(text)
1178
c.drawText(textobject)
1180
def getSpaceBefore(self):
1182
# print "got space before", self.spaceBefore
1183
return self.style.spaceBefore
1185
def getSpaceAfter(self):
1186
#print "got space after", self.spaceAfter
1187
return self.style.spaceAfter
1189
def defaultContext():
1191
from reportlab.lib.styles import getSampleStyleSheet
1192
styles = getSampleStyleSheet()
1193
for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items():
1194
result[stylenamekey] = styles[stylenamevalue]
1197
def buildContext(stylesheet=None):
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]
1206
for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items():
1207
if stylesheet.has_key(stylenamevalue):
1208
result[stylenamekey] = stylesheet[stylenamevalue]
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]
1217
class Para(Flowable):
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)
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
1242
self.face = style.fontName
1243
self.size = style.fontSize
1245
def getSpaceBefore(self):
1247
# print "got space before", self.spaceBefore
1248
return self.spaceBefore
1250
def getSpaceAfter(self):
1251
#print "got space after", self.spaceAfter
1252
return self.spaceAfter
1254
def wrap(self, availableWidth, availableHeight):
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
1266
leading = state["leading"]
1268
leading = self.style1.leading
1269
program = self.program
1270
self.cansplit = 1 # until proven otherwise
1275
needatleast = state["leading"]
1277
needatleast = self.style1.leading
1278
if availableHeight<=needatleast:
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"
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
1295
heightused = self.height
1297
# too big if there is a remainder
1299
# lie about the height: it must be split anyway
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
1312
self.remainder = None # no extra
1314
if height>availableHeight:
1315
height = availableHeight-0.1
1317
# print "giving height", height, "of", availableHeight, self.parsedText
1318
result = (availableWidth, height)
1321
if abs(availableHeight-h)<0.2:
1322
print "exact match???" + repr(availableHeight, h)
1323
print "wrap is", (availableWidth, availableHeight), result
1326
def split(self, availableWidth, availableHeight):
1328
# print "SPLITTING", id(self), availableWidth, availableHeight
1329
if availableHeight<=0 or not self.cansplit:
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
1341
remainder = self.remainder
1344
result= [self, remainder]
1347
#if debug: print "split is", result
1351
p = self.myengine #paragraphEngine()
1352
formattedProgram = self.formattedProgram
1353
if formattedProgram is None:
1354
raise ValueError, "must call wrap before draw"
1356
laststate = self.laststate
1363
# print id(self), "page number", c.getPageNumber()
1364
height = self.height
1366
leading = state["leading"]
1368
leading = self.style1.leading
1370
# c.rect(0,0,-1, height-self.size, fill=1, stroke=1)
1371
c.translate(0, height-self.size)
1373
#t.setTextOrigin(0,0)
1374
if DUMPPROGRAM or debug:
1375
print "="*44, "now running program"
1376
for x in formattedProgram:
1379
laststate = p.runOpCodes(formattedProgram, c, t)
1380
#print laststate["x"], laststate["y"]
1383
def compileProgram(self, parsedText, program=None):
1385
# standard parameters
1386
#program = self.program
1391
# add style information if there was no initial state
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) )
1404
a( ("color", style.textColor) )
1405
#a( ("nextLine", 0) ) # clear for next line
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:
1415
if tx in (StringType, UnicodeType, InstanceType):
1417
program.insert( count, ("indent", -style.firstLineIndent ) ) # defaults to end if no visibles
1418
#print "="*8, id(self), "program is"
1422
## # check pushes and pops
1425
## for x in program:
1428
## if type(x) is TupleType:
1431
## stackcount = stackcount+1
1432
## print " "*stackcount, "push", stackcount
1434
## stackcount = stackcount-1
1435
## print " "*stackcount, "pop", stackcount
1438
## print "STACK UNDERFLOW!"
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
1450
program.append( ("push",) )
1451
if style.spaceBefore:
1452
program.append( ("leading", style.spaceBefore+style.leading) )
1454
program.append( ("leading", style.leading) )
1455
program.append( ("nextLine", 0) )
1456
program = self.compileProgram(parsedText, program=program)
1457
program.append( ("pop",) )
1459
program.append( ("push",) )
1460
if style.spaceAfter:
1461
program.append( ("leading", style.spaceAfter) )
1463
program.append( ("leading", 0) )
1464
program.append( ("nextLine", 0) )
1465
program.append( ("pop",) )
1467
def compileComponent(self, parsedText, program):
1469
ttext = type(parsedText)
1470
#program = self.program
1471
if ttext in (StringType, UnicodeType):
1472
# handle special characters here...
1475
stext = parsedText.strip()
1477
program.append(" ") # contract whitespace to single space
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
1487
compilername = "compile_"+tagname
1488
compiler = getattr(self, compilername, None)
1489
if compiler is not None:
1490
compiler(attdict, content, extra, program)
1492
# just pass the tag through
1494
L = [ "<" + tagname ]
1496
if not attdict: attdict = {}
1497
for (k, v) in attdict.items():
1502
a("</%s>" % tagname)
1506
handleSpecialCharacters(self, t, program)
1508
raise ValueError, "don't know how to handle tag " + repr(tagname)
1510
def shiftfont(self, program, face=None, bold=None, italic=None):
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
1520
self.italic = italic
1521
from reportlab.lib.fonts import tt2ps
1522
font = tt2ps(face,bold,italic)
1523
oldfont = tt2ps(oldface,oldbold,olditalic)
1525
program.append( ("face", font ) )
1528
def compile_(self, attdict, content, extra, program):
1529
# "anonymous" tag: just do the content
1531
self.compileComponent(e, program)
1532
#compile_para = compile_ # at least for now...
1534
def compile_pageNumber(self, attdict, content, extra, program):
1535
program.append(PageNumberObject())
1537
def compile_b(self, attdict, content, extra, program):
1538
(f,b,i) = self.shiftfont(program, bold=1)
1540
self.compileComponent(e, program)
1541
self.shiftfont(program, bold=b)
1543
def compile_i(self, attdict, content, extra, program):
1544
(f,b,i) = self.shiftfont(program, italic=1)
1546
self.compileComponent(e, program)
1547
self.shiftfont(program, italic=i)
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) )
1554
self.compileComponent(e, program)
1555
program.append( ('endLineOperation', UNDERLINE) )
1557
def compile_sub(self, attdict, content, extra, program):
1559
self.size = newsize = size * 0.7
1561
#program = self.program
1562
program.append( ('size', newsize) )
1564
program.append( ('rise', -rise) )
1566
self.compileComponent(e, program)
1567
program.append( ('size', size) )
1568
program.append( ('rise', rise) )
1570
def compile_ul(self, attdict, content, extra, program, tagname="ul"):
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
1578
if te in (StringType, UnicodeType):
1580
raise ValueError, "don't expect CDATA between list elements"
1581
elif te is TupleType:
1582
(tagname, attdict1, content1, extra) = e
1584
raise ValueError, "don't expect %s inside list" % repr(tagname)
1585
newatts = atts.copy()
1587
newatts.update(attdict1)
1588
bulletmaker.makeBullet(newatts)
1589
self.compile_para(newatts, content1, extra, program)
1591
def compile_ol(self, attdict, content, extra, program):
1592
return self.compile_ul(attdict, content, extra, program, tagname="ol")
1594
def compile_dl(self, attdict, content, extra, program):
1596
#print "compile", tagname, attdict
1597
atts = attdict.copy()
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
1609
if te in (StringType, UnicodeType):
1611
raise ValueError, "don't expect CDATA between list elements"
1612
elif not contentcopy:
1613
break # done at ending whitespace
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'" % \
1623
raise ValueError, "dt will not be displayed unless followed by a dd: "+repr(bullet)
1625
self.compile_para(attdict1, content1, extra, program)
1626
# raise ValueError, \
1627
# "only simple strings supported in dd content currently: "+repr(content1)
1629
newatts = atts.copy()
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
1636
raise ValueError, "dt will not be displayed unless followed by a dd"+repr(bullet)
1638
def compile_super(self, attdict, content, extra, program):
1640
self.size = newsize = size * 0.7
1642
#program = self.program
1643
program.append( ('size', newsize) )
1644
program.append( ('rise', rise) )
1646
self.compileComponent(e, program)
1647
program.append( ('size', size) )
1649
program.append( ('rise', -rise) )
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
1658
font = tt2ps(face,self.bold,self.italic)
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) )
1670
self.compileComponent(e, program)
1671
program.append( ("pop",) ) # restore as before
1673
def compile_a(self, attdict, content, extra, program):
1674
url = attdict["href"]
1675
colorname = attdict.get("color", "blue")
1676
#program = self.program
1678
program.append( ("push",) ) # store current data
1679
program.append( ("color", colorname) )
1680
program.append( ('lineOperation', Link) )
1681
program.append( ('lineOperation', UNDERLINE) )
1683
self.compileComponent(e, program)
1684
program.append( ('endLineOperation', UNDERLINE) )
1685
program.append( ('endLineOperation', Link) )
1686
program.append( ("pop",) ) # restore as before
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
1695
program.append( ("color", colorname) )
1696
program.append( ('lineOperation', Link) )
1697
program.append( ('lineOperation', UNDERLINE) )
1699
self.compileComponent(e, program)
1700
program.append( ('endLineOperation', UNDERLINE) )
1701
program.append( ('endLineOperation', Link) )
1702
program.append( ("pop",) ) # restore as before
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
1711
program.append( ("color", colorname) )
1712
program.append( ('lineOperation', Link) )
1714
program.append( ('lineOperation', UNDERLINE) )
1716
self.compileComponent(e, program)
1718
program.append( ('endLineOperation', UNDERLINE) )
1719
program.append( ('endLineOperation', Link) )
1720
program.append( ("pop",) ) # restore as before
1722
#def compile_p(self, attdict, content, extra, program):
1723
# # have to be careful about base indent here!
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"
1731
self.do_bullet(text, program)
1733
def do_bullet(self, text, program):
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) )
1741
def compile_tt(self, attdict, content, extra, program):
1742
(f,b,i) = self.shiftfont(program, face="Courier")
1744
self.compileComponent(e, program)
1745
self.shiftfont(program, face=f)
1747
def compile_greek(self, attdict, content, extra, program):
1748
self.compile_font({"face": "symbol"}, content, extra, program)
1750
def compile_evalString(self, attdict, content, extra, program):
1751
program.append( EvalStringObject(attdict, content, extra, self.context) )
1753
def compile_name(self, attdict, content, extra, program):
1754
program.append( NameObject(attdict, content, extra, self.context) )
1756
def compile_getName(self, attdict, content, extra, program):
1757
program.append( GetNameObject(attdict, content, extra, self.context) )
1759
def compile_seq(self, attdict, content, extra, program):
1760
program.append( SeqObject(attdict, content, extra, self.context) )
1762
def compile_seqReset(self, attdict, content, extra, program):
1763
program.append( SeqResetObject(attdict, content, extra, self.context) )
1765
def compile_seqDefault(self, attdict, content, extra, program):
1766
program.append( SeqDefaultObject(attdict, content, extra, self.context) )
1768
def compile_para(self, attdict, content, extra, program, stylename = "para.defaultStyle"):
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
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)
1806
def makeBullet(self, atts, bl=None):
1807
count = self.count = self.count+1
1809
tagname = self.tagname
1810
#print "makeBullet", tagname, typ, count
1811
# forget space before for non-first elements
1813
atts["spaceBefore"] = "0"
1816
if typ=="disc": bl = chr(109)
1817
elif typ=="circle": bl = chr(108)
1818
elif typ=="square": bl = chr(110)
1820
raise ValueError, "unordered list type %s not implemented" % repr(typ)
1821
if not atts.has_key("bulletFontName"):
1822
atts["bulletFontName"] = "ZapfDingbats"
1824
if typ=="1": bl = repr(count)
1826
theord = ord("a")+count-1
1829
theord = ord("A")+count-1
1832
raise ValueError, "ordered bullet type %s not implemented" % repr(typ)
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
1840
class EvalStringObject:
1841
"this will only work if rml2pdf is present"
1843
tagname = "evalString"
1845
def __init__(self, attdict, content, extra, context):
1848
self.attdict = attdict
1849
self.content = content
1850
self.context = context
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, {})
1859
def width(self, engine):
1860
from reportlab.pdfbase.pdfmetrics import stringWidth
1861
content = self.content
1864
tuple = (self.tagname, self.attdict, content, self.extra)
1865
op = self.op = self.getOp(tuple, engine)
1870
return stringWidth(s, engine.fontName, engine.fontSize)
1872
def execute(self, engine, textobject, canvas):
1873
textobject.textOut(str(self.op))
1875
class SeqObject(EvalStringObject):
1877
def getOp(self, tuple, engine):
1878
from reportlab.lib.sequencer import getSequencer
1879
globalsequencer = getSequencer()
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
1887
elif attr.has_key('id'):
1891
op = self.op = globalsequencer.nextf(id)
1894
class NameObject(EvalStringObject):
1896
def execute(self, engine, textobject, canvas):
1897
pass # name doesn't produce any output
1899
class SeqDefaultObject(NameObject):
1901
def getOp(self, tuple, engine):
1902
from reportlab.lib.sequencer import getSequencer
1903
globalsequencer = getSequencer()
1906
default = attr['id']
1909
globalsequencer.setDefaultCounter(default)
1913
class SeqResetObject(NameObject):
1915
def getOp(self, tuple, engine):
1916
from reportlab.lib.sequencer import getSequencer
1918
globalsequencer = getSequencer()
1925
base = math.atoi(attr['base'])
1928
globalsequencer.reset(id, base)
1932
class GetNameObject(EvalStringObject):
1935
class PageNumberObject:
1937
def __init__(self, example="XXX"):
1938
self.example = example # XXX SHOULD ADD THE ABILITY TO PASS IN EXAMPLES
1940
def width(self, engine):
1941
from reportlab.pdfbase.pdfmetrics import stringWidth
1942
return stringWidth(self.example, engine.fontName, engine.fontSize)
1944
def execute(self, engine, textobject, canvas):
1945
n = canvas.getPageNumber()
1946
textobject.textOut(str(n))
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
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?
1967
if not bulletText and len(content)==1:
1969
if type(text) in (StringType, UnicodeType) and "&" not in text:
1970
result = FastPara(mystyle, text)
1972
result = Para(mystyle, content, context=context, bulletText=bulletText) # possible ref loop on context, break later
1975
theParaMapper = paraMapper()
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)
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
1997
<b>This is bold text.</b>
1999
<i>This is italic text.</i>
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
2011
<li> this is an element at 2
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
2017
<li> this is an element at 3
2019
more text and even more text and on and on and so forth
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>
2028
more text and even more text and on and on and so forth
2031
<li> this is an element at 4
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
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
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
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>
2054
<li>first element of the alpha list
2057
<li>first element of the square unnumberred list</li>
2059
<li>second element of the unnumberred list</li>
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
2070
<li>fourth element of the unnumberred list</li>
2076
<li>second element of the alpha list</li>
2078
<li>third element of the alpha list
2079
third element of the unnumberred list &#33; --> !
2080
third element of the unnumberred list &#8704; --> ∀
2081
third element of the unnumberred list &exist; --> ∃
2082
third element of the unnumberred list
2083
third element of the unnumberred list
2084
third element of the unnumberred list
2087
<li>fourth element of the alpha list</li>
2095
<a href="http://www.reportlab.com">goto www.reportlab.com</a>.
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
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)
2121
canv.setStrokeColorRGB(1, 0, 0)
2122
#canv.translate(0, 3*inch)
2123
canv.rect(0,0,w,-h, fill=0, stroke=1)
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:
2132
#print "handling", repr(text)
2134
if 0 and "&" not in text:
2136
for x in text.split():
2137
result.append(x+" ")
2140
if text[-1:] not in whitespace:
2141
result[-1] = last.strip()
2142
program.extend(result)
2146
amptext = text.split("&")
2148
lastfrag = amptext[-1]
2149
for fragment in amptext:
2151
# check for special chars
2152
semi = fragment.find(";")
2154
name = fragment[:semi]
2158
n = atoi(name[2:], 16)
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
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
2185
fragment = "&"+fragment
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]+" " )
2199
last = sfragment[-1].strip()
2201
#print "last is", repr(last)
2202
program.append( last )
2204
#print "HANDLED", program
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.
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)
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)
2220
class UnderLineHandler:
2221
def __init__(self, color=None):
2223
def start_at(self, x,y, para, canvas, textobject):
2226
def end_at(self, x, y, para, canvas, textobject):
2227
offset = para.fontSize/8.0
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()
2236
UNDERLINE = UnderLineHandler()
2238
class HotLink(UnderLineHandler):
2240
def __init__(self, url):
2243
def end_at(self, x, y, para, canvas, textobject):
2244
fontsize = para.fontSize
2245
rect = [self.xStart, self.yStart, x,y+fontsize]
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)
2251
def link(self, rect, canvas):
2252
canvas.linkURL(self.url, rect, relative=1)
2254
class InternalLink(HotLink):
2256
def link(self, rect, canvas):
2257
destinationname = self.url
2259
canvas.linkRect(contents, destinationname, rect, Border="[0 0 0]")
2261
class DefDestination(HotLink):
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
2272
def splitspace(text):
2273
# split on spacing but include spaces at element ends
2274
stext = text.split()
2277
result.append(e+" ")
2280
testlink = HotLink("http://www.reportlab.com")
2285
('rightIndent', 200),
2286
('bullet', 'very long bullet', 50, 'Courier', 14),
2287
('align', TA_CENTER),
2288
('face', "Times-Roman"),
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),
2297
('bullet', 'Bullet', 10, 'Courier', 8),
2298
('face', "Times-Roman"),
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. ") + [
2303
('align', TA_JUSTIFY),
2304
('bullet', 'Bullet not quite as long this time', 50, 'Courier', 8),
2305
('face', "Helvetica-Oblique"),
2310
] + splitspace("This is the THIRD!!! segment of the first paragraph."
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),
2318
"lowered ", "text ",
2323
('rightIndent', 50),
2325
('align', TA_RIGHT),
2326
('bullet', 'O', 50, 'Courier', 14),
2327
('face', "Helvetica"),
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 ") + [
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)
2345
remainder = test_program + test_program + test_program
2349
c.translate(inch, 8*inch)
2351
t.setTextOrigin(0,0)
2352
p = paragraphEngine()
2353
p.resetState(laststate)
2358
(formattedprogram, remainder, laststate, height) = p.format(maxwidth, maxheight, remainder)
2360
pprint( formattedprogram )#; return
2361
laststate = p.runOpCodes(formattedprogram, c, t)
2364
print "="*30, "x=", laststate["x"], "y=", laststate["y"]
2368
if __name__=="__main__":