253
|
1 |
###############################################################################
|
|
2 |
#
|
|
3 |
# ReportLab Public License Version 1.0
|
|
4 |
#
|
|
5 |
# Except for the change of names the spirit and intention of this
|
|
6 |
# license is the same as that of Python
|
|
7 |
#
|
|
8 |
# (C) Copyright ReportLab Inc. 1998-2000.
|
|
9 |
#
|
|
10 |
#
|
|
11 |
# All Rights Reserved
|
|
12 |
#
|
|
13 |
# Permission to use, copy, modify, and distribute this software and its
|
|
14 |
# documentation for any purpose and without fee is hereby granted, provided
|
|
15 |
# that the above copyright notice appear in all copies and that both that
|
|
16 |
# copyright notice and this permission notice appear in supporting
|
|
17 |
# documentation, and that the name of ReportLab not be used
|
|
18 |
# in advertising or publicity pertaining to distribution of the software
|
|
19 |
# without specific, written prior permission.
|
|
20 |
#
|
|
21 |
#
|
|
22 |
# Disclaimer
|
|
23 |
#
|
|
24 |
# ReportLab Inc. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
|
25 |
# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
|
|
26 |
# IN NO EVENT SHALL ReportLab BE LIABLE FOR ANY SPECIAL, INDIRECT
|
|
27 |
# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
28 |
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
29 |
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
30 |
# PERFORMANCE OF THIS SOFTWARE.
|
|
31 |
#
|
|
32 |
###############################################################################
|
|
33 |
# $Log: flowables.py,v $
|
|
34 |
# Revision 1.1 2000/06/01 15:23:06 rgbecker
|
|
35 |
# Platypus re-organisation
|
|
36 |
#
|
|
37 |
#
|
|
38 |
__version__=''' $Id: flowables.py,v 1.1 2000/06/01 15:23:06 rgbecker Exp $ '''
|
|
39 |
__doc__="""
|
|
40 |
"""
|
|
41 |
|
|
42 |
# 200-10-13 gmcm
|
|
43 |
# packagizing
|
|
44 |
# rewrote grid stuff - now in tables.py
|
|
45 |
import os
|
|
46 |
import string
|
|
47 |
from copy import deepcopy
|
|
48 |
|
|
49 |
|
|
50 |
from reportlab.pdfgen import canvas
|
|
51 |
from reportlab.lib.units import inch
|
|
52 |
from reportlab.lib.colors import red
|
|
53 |
from reportlab.pdfbase import pdfutils
|
|
54 |
|
|
55 |
from reportlab.lib.pagesizes import DEFAULT_PAGE_SIZE
|
|
56 |
PAGE_HEIGHT = DEFAULT_PAGE_SIZE[1]
|
|
57 |
|
|
58 |
#############################################################
|
|
59 |
# Flowable Objects - a base class and a few examples.
|
|
60 |
# One is just a box to get some metrics. We also have
|
|
61 |
# a paragraph, an image and a special 'page break'
|
|
62 |
# object which fills the space.
|
|
63 |
#############################################################
|
|
64 |
class Flowable:
|
|
65 |
"""Abstract base class for things to be drawn. Key concepts:
|
|
66 |
1. It knows its size
|
|
67 |
2. It draws in its own coordinate system (this requires the
|
|
68 |
base API to provide a translate() function.
|
|
69 |
"""
|
|
70 |
def __init__(self):
|
|
71 |
self.width = 0
|
|
72 |
self.height = 0
|
|
73 |
self.wrapped = 0
|
|
74 |
|
|
75 |
def drawOn(self, canvas, x, y):
|
|
76 |
"Tell it to draw itself on the canvas. Do not override"
|
|
77 |
self.canv = canvas
|
|
78 |
self.canv.saveState()
|
|
79 |
self.canv.translate(x, y)
|
|
80 |
|
|
81 |
self.draw() #this is the bit you overload
|
|
82 |
|
|
83 |
self.canv.restoreState()
|
|
84 |
del self.canv
|
|
85 |
|
|
86 |
|
|
87 |
def wrap(self, availWidth, availHeight):
|
|
88 |
"""This will be called by the enclosing frame before objects
|
|
89 |
are asked their size, drawn or whatever. It returns the
|
|
90 |
size actually used."""
|
|
91 |
return (self.width, self.height)
|
|
92 |
|
|
93 |
def split(self, availWidth, availheight):
|
|
94 |
"""This will be called by more sophisticated frames when
|
|
95 |
wrap fails. Stupid flowables should return []. Clever flowables
|
|
96 |
should split themselves and return a list of flowables"""
|
|
97 |
return []
|
|
98 |
|
|
99 |
def getSpaceAfter(self):
|
|
100 |
if hasattr(self,'spaceAfter'): return self.spaceAfter
|
|
101 |
elif hasattr(self,'style') and hasattr(self.style,'spaceAfter'): return self.style.spaceAfter
|
|
102 |
else: return 0
|
|
103 |
|
|
104 |
def getSpaceBefore(self):
|
|
105 |
if hasattr(self,'spaceBefore'): return self.spaceBefore
|
|
106 |
elif hasattr(self,'style') and hasattr(self.style,'spaceBefore'): return self.style.spaceBefore
|
|
107 |
else: return 0
|
|
108 |
|
|
109 |
class XBox(Flowable):
|
|
110 |
"""Example flowable - a box with an x through it and a caption.
|
|
111 |
This has a known size, so does not need to respond to wrap()."""
|
|
112 |
def __init__(self, width, height, text = 'A Box'):
|
|
113 |
Flowable.__init__(self)
|
|
114 |
self.width = width
|
|
115 |
self.height = height
|
|
116 |
self.text = text
|
|
117 |
|
|
118 |
def draw(self):
|
|
119 |
self.canv.rect(0, 0, self.width, self.height)
|
|
120 |
self.canv.line(0, 0, self.width, self.height)
|
|
121 |
self.canv.line(0, self.height, self.width, 0)
|
|
122 |
|
|
123 |
#centre the text
|
|
124 |
self.canv.setFont('Times-Roman',12)
|
|
125 |
self.canv.drawCentredString(0.5*self.width, 0.5*self.height, self.text)
|
|
126 |
|
|
127 |
class Preformatted(Flowable):
|
|
128 |
"""This is like the HTML <PRE> tag. The line breaks are exactly where you put
|
|
129 |
them, and it will not be wrapped. So it is much simpler to implement!"""
|
|
130 |
def __init__(self, text, style, bulletText = None, dedent=0):
|
|
131 |
self.style = style
|
|
132 |
self.bulletText = bulletText
|
|
133 |
|
|
134 |
#tidy up text - carefully, it is probably code. If people want to
|
|
135 |
#indent code within a source script, you can supply an arg to dedent
|
|
136 |
#and it will chop off that many character, otherwise it leaves
|
|
137 |
#left edge intact.
|
|
138 |
|
|
139 |
templines = string.split(text, '\n')
|
|
140 |
self.lines = []
|
|
141 |
for line in templines:
|
|
142 |
line = string.rstrip(line[dedent:])
|
|
143 |
self.lines.append(line)
|
|
144 |
#don't want the first or last to be empty
|
|
145 |
while string.strip(self.lines[0]) == '':
|
|
146 |
self.lines = self.lines[1:]
|
|
147 |
while string.strip(self.lines[-1]) == '':
|
|
148 |
self.lines = self.lines[:-1]
|
|
149 |
|
|
150 |
def wrap(self, availWidth, availHeight):
|
|
151 |
self.width = availWidth
|
|
152 |
self.height = self.style.leading*len(self.lines)
|
|
153 |
return (self.width, self.height)
|
|
154 |
|
|
155 |
def split(self, availWidth, availHeight):
|
|
156 |
#returns two Preformatted objects
|
|
157 |
|
|
158 |
#not sure why they can be called with a negative height
|
|
159 |
if availHeight < self.style.leading:
|
|
160 |
return []
|
|
161 |
|
|
162 |
linesThatFit = int(availHeight * 1.0 / self.style.leading)
|
|
163 |
|
|
164 |
text1 = string.join(self.lines[0:linesThatFit], '\n')
|
|
165 |
text2 = string.join(self.lines[linesThatFit:], '\n')
|
|
166 |
style = self.style
|
|
167 |
if style.firstLineIndent != 0:
|
|
168 |
style = deepcopy(style)
|
|
169 |
style.firstLineIndent = 0
|
|
170 |
return [Preformatted(text1, self.style), Preformatted(text2, style)]
|
|
171 |
|
|
172 |
|
|
173 |
def draw(self):
|
|
174 |
#call another method for historical reasons. Besides, I
|
|
175 |
#suspect I will be playing with alternate drawing routines
|
|
176 |
#so not doing it here makes it easier to switch.
|
|
177 |
|
|
178 |
cur_x = self.style.leftIndent
|
|
179 |
cur_y = self.height - self.style.fontSize
|
|
180 |
self.canv.addLiteral('%PreformattedPara')
|
|
181 |
if self.style.textColor:
|
|
182 |
self.canv.setFillColor(self.style.textColor)
|
|
183 |
tx = self.canv.beginText(cur_x, cur_y)
|
|
184 |
#set up the font etc.
|
|
185 |
tx.setFont(self.style.fontName,
|
|
186 |
self.style.fontSize,
|
|
187 |
self.style.leading)
|
|
188 |
|
|
189 |
for text in self.lines:
|
|
190 |
tx.textLine(text)
|
|
191 |
self.canv.drawText(tx)
|
|
192 |
|
|
193 |
class Image(Flowable):
|
|
194 |
def __init__(self, filename, width=None, height=None):
|
|
195 |
"""If size to draw at not specified, get it from the image."""
|
|
196 |
import Image #this will raise an error if they do not have PIL.
|
|
197 |
self.filename = filename
|
|
198 |
# if it is a JPEG, will be inlined within the file -
|
|
199 |
# but we still need to know its size now
|
|
200 |
if os.path.splitext(filename)[1] in ['.jpg', '.JPG', '.jpeg', '.JPEG']:
|
|
201 |
info = pdfutils.readJPEGInfo(open(filename, 'rb'))
|
|
202 |
self.imageWidth = info[0]
|
|
203 |
self.imageHeight = info[1]
|
|
204 |
else:
|
|
205 |
img = Image.open(filename)
|
|
206 |
(self.imageWidth, self.imageHeight) = img.size
|
|
207 |
if width:
|
|
208 |
self.drawWidth = width
|
|
209 |
else:
|
|
210 |
self.drawWidth = self.imageWidth
|
|
211 |
if height:
|
|
212 |
self.drawHeight = height
|
|
213 |
else:
|
|
214 |
self.drawHeight = self.imageHeight
|
|
215 |
|
|
216 |
def wrap(self, availWidth, availHeight):
|
|
217 |
#the caller may decide it does not fit.
|
|
218 |
self.availWidth = availWidth
|
|
219 |
return (self.drawWidth, self.drawHeight)
|
|
220 |
|
|
221 |
def draw(self):
|
|
222 |
#center it
|
|
223 |
startx = 0.5 * (self.availWidth - self.drawWidth)
|
|
224 |
self.canv.drawInlineImage(self.filename,
|
|
225 |
startx,
|
|
226 |
0,
|
|
227 |
self.drawWidth,
|
|
228 |
self.drawHeight
|
|
229 |
)
|
|
230 |
class Spacer(Flowable):
|
|
231 |
"""A spacer just takes up space and doesn't draw anything - it can
|
|
232 |
ensure a gap between objects."""
|
|
233 |
def __init__(self, width, height):
|
|
234 |
self.width = width
|
|
235 |
self.height = height
|
|
236 |
|
|
237 |
def wrap(self, availWidth, availHeight):
|
|
238 |
return (self.width, self.height)
|
|
239 |
|
|
240 |
def draw(self):
|
|
241 |
pass
|
|
242 |
|
|
243 |
class PageBreak(Flowable):
|
|
244 |
"""This works by consuming all remaining space in the frame!"""
|
|
245 |
|
|
246 |
def wrap(self, availWidth, availHeight):
|
|
247 |
self.width = availWidth
|
|
248 |
self.height = availHeight
|
|
249 |
return (availWidth,availHeight) #step back a point
|
|
250 |
|
|
251 |
def draw(self):
|
|
252 |
pass
|
|
253 |
|
|
254 |
class Macro(Flowable):
|
|
255 |
"""This is not actually drawn (i.e. it has zero height)
|
|
256 |
but is executed when it would fit in the frame. Allows direct
|
|
257 |
access to the canvas through the object 'canvas'"""
|
|
258 |
def __init__(self, command):
|
|
259 |
self.command = command
|
|
260 |
def wrap(self, availWidth, availHeight):
|
|
261 |
return (0,0)
|
|
262 |
def draw(self):
|
|
263 |
exec self.command in globals(), {'canvas':self.canv}
|
|
264 |
|
|
265 |
from paragraph import Paragraph
|