197
|
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: doctemplate.py,v $
|
200
|
34 |
# Revision 1.3 2000/05/12 14:53:38 rgbecker
|
|
35 |
# Handle splitting error in build
|
|
36 |
#
|
199
|
37 |
# Revision 1.2 2000/05/12 14:45:31 rgbecker
|
|
38 |
# Single actions only in ActionFlowables
|
200
|
39 |
#
|
197
|
40 |
# Revision 1.1 2000/05/12 12:53:33 rgbecker
|
|
41 |
# Initial try at a document template class
|
199
|
42 |
#
|
200
|
43 |
__version__=''' $Id: doctemplate.py,v 1.3 2000/05/12 14:53:38 rgbecker Exp $ '''
|
197
|
44 |
__doc__="""
|
|
45 |
More complicated Document model
|
|
46 |
"""
|
|
47 |
from layout import *
|
|
48 |
from types import *
|
|
49 |
import sys
|
|
50 |
|
|
51 |
class ActionFlowable(Flowable):
|
|
52 |
'''This Flowable is never drawn, it can be used for data driven controls'''
|
199
|
53 |
def __init__(self,action=[]):
|
|
54 |
if type(action) not in (ListType, TupleType):
|
|
55 |
action = (action,)
|
|
56 |
self.action = action
|
197
|
57 |
|
|
58 |
def wrap(self, availWidth, availHeight):
|
|
59 |
raise NotImplementedError
|
|
60 |
|
|
61 |
def draw(self):
|
|
62 |
raise NotImplementedError
|
|
63 |
|
|
64 |
def apply(self,doc):
|
199
|
65 |
action = self.action[0]
|
|
66 |
args = tuple(self.action[1:])
|
|
67 |
try:
|
|
68 |
apply(getattr(doc,'handle_'+action), args)
|
|
69 |
except AttributeError:
|
|
70 |
raise NotImplementedError, "Can't handle ActionFlowable(%s)" % action
|
|
71 |
except:
|
|
72 |
t, v, None = sys.exc_info()
|
|
73 |
raise t, "%s\n handle_%s args=%s"%(v,action,args)
|
197
|
74 |
|
|
75 |
|
|
76 |
FrameBreak = ActionFlowable('frameBegin')
|
|
77 |
PageBegin = ActionFlowable('pageBegin')
|
|
78 |
|
|
79 |
class NextPageTemplate(ActionFlowable):
|
|
80 |
def __init__(self,pt):
|
199
|
81 |
ActionFlowable.__init__(self,('nextPageTemplate',pt))
|
197
|
82 |
|
|
83 |
class PageTemplate:
|
|
84 |
"""
|
|
85 |
essentially a list of BasicFrames and an onPage routine to call at the start
|
|
86 |
of a page when this is selected.
|
|
87 |
"""
|
|
88 |
def __init__(self,id=None,frames=[],onPage=None):
|
|
89 |
if type(frames) not in (ListType,TupleType): frames = [frames]
|
|
90 |
assert filter(lambda x: not isinstance(x,BasicFrame), frames)==[], "frames argument error"
|
|
91 |
self.id = id
|
|
92 |
self.frames = frames
|
|
93 |
self.onPage = onPage or _doNothing
|
|
94 |
|
|
95 |
class BaseDocTemplate:
|
|
96 |
"""
|
|
97 |
First attempt at defining a document template class.
|
|
98 |
|
|
99 |
The basic idea is simple.
|
|
100 |
0) The document has a list of data associated with it
|
|
101 |
this data should derive from flowables. We'll have
|
|
102 |
special classes like PageBreak, FrameBreak to do things
|
|
103 |
like forcing a page end etc.
|
|
104 |
|
|
105 |
1) The document has one or more page templates.
|
|
106 |
|
|
107 |
2) Each page template has one or more frames.
|
|
108 |
|
|
109 |
3) The document class provides base methods for handling the
|
|
110 |
story events and some reasonable methods for getting the
|
|
111 |
story flowables into the frames.
|
|
112 |
|
|
113 |
4) The document instances can override the base handler routines.
|
|
114 |
"""
|
|
115 |
def __init__(self, filename, pagesize=DEFAULT_PAGE_SIZE, pageTemplates=[], showBoundary=0,
|
|
116 |
leftMargin=inch, rightMargin=inch, topMargin=inch, bottomMargin=inch):
|
|
117 |
|
|
118 |
self.pageTemplates = []
|
|
119 |
self.addPageTemplates(pageTemplates)
|
|
120 |
self.filename = filename
|
|
121 |
self.showBoundary = showBoundary
|
|
122 |
self.leftMargin = leftMargin
|
|
123 |
self.bottomMargin = bottomMargin
|
|
124 |
self.rightMargin = pagesize[0] - rightMargin
|
|
125 |
self.topMargin = pagesize[1] - topMargin
|
|
126 |
self.width = self.rightMargin - self.leftMargin
|
|
127 |
self.height = self.topMargin - self.bottomMargin
|
|
128 |
self.pagesize = pagesize
|
|
129 |
|
|
130 |
def clean_hanging(self):
|
|
131 |
while len(self._hanging):
|
|
132 |
self.handle_flowable(self._hanging)
|
|
133 |
|
|
134 |
def addPageTemplates(self,pageTemplates):
|
|
135 |
if type(pageTemplates) not in (ListType,TupleType):
|
|
136 |
pageTemplates = [pageTemplates]
|
|
137 |
assert filter(lambda x: not isinstance(x,PageTemplate), pageTemplates)==[], "pageTemplates argument error"
|
|
138 |
for t in pageTemplates:
|
|
139 |
self.pageTemplates.append(t)
|
|
140 |
|
|
141 |
def handle_documentBegin(self):
|
|
142 |
self._hanging = [PageBegin]
|
|
143 |
self.pageTemplate = self.pageTemplates[0]
|
|
144 |
self.page = 0
|
|
145 |
|
|
146 |
def handle_pageBegin(self):
|
|
147 |
'''shouldn't normally be called directly'''
|
|
148 |
self.page = self.page + 1
|
|
149 |
self.pageTemplate.onPage(self.canv,self)
|
|
150 |
if hasattr(self,'_nextFrameIndex'):
|
|
151 |
del self._nextFrameIndex
|
|
152 |
self.frame = self.pageTemplate.frames[0]
|
|
153 |
self.handle_frameBegin()
|
|
154 |
|
|
155 |
def handle_pageEnd(self):
|
|
156 |
''' show the current page
|
|
157 |
check the next page template
|
|
158 |
hang a page begin
|
|
159 |
'''
|
|
160 |
self.canv.showPage()
|
|
161 |
if hasattr(self,'_nextPageTemplateIndex'):
|
|
162 |
self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex]
|
|
163 |
del self._nextPageTemplateIndex
|
|
164 |
self._hanging.append(PageBegin)
|
|
165 |
|
|
166 |
def handle_pageBreak(self):
|
|
167 |
'''some might choose not to end all the frames'''
|
|
168 |
if 1:
|
|
169 |
self.handle_pageEnd()
|
|
170 |
else:
|
|
171 |
n = len(self._hanging)
|
|
172 |
while len(self._hanging)==n:
|
|
173 |
self.handle_frameEnd()
|
|
174 |
|
|
175 |
def handle_frameBegin(self,*args):
|
|
176 |
self.frame._reset()
|
|
177 |
if self.showBoundary:
|
|
178 |
self.canv.rect(
|
|
179 |
self.frame.x1,
|
|
180 |
self.frame.y1,
|
|
181 |
self.frame.x2 - self.frame.x1,
|
|
182 |
self.frame.y2 - self.frame.y1
|
|
183 |
)
|
|
184 |
|
|
185 |
def handle_frameEnd(self):
|
|
186 |
''' Handles the semantics of the end of a frame. This includes the selection of
|
|
187 |
the next frame or if this is the last frame then invoke pageEnd.
|
|
188 |
'''
|
|
189 |
if hasattr(self,'_nextFrameIndex'):
|
|
190 |
frame = self.pageTemplate.frames[self._nextFrameIndex]
|
|
191 |
del self._nextFrameIndex
|
|
192 |
elif hasattr(self.frame,'lastFrame') or self.frame is self.pageTemplate.frames[-1]:
|
|
193 |
self.handle_pageEnd()
|
|
194 |
else:
|
|
195 |
f = self.frame
|
|
196 |
self.frame = self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1]
|
|
197 |
self.handle_frameBegin()
|
|
198 |
|
|
199 |
def handle_nextPageTemplate(self,pt):
|
|
200 |
'''On endPage chenge to the page template with name or index pt'''
|
|
201 |
if type(pt) is StringType:
|
|
202 |
for t in self.pageTemplates:
|
|
203 |
if t.id == pt:
|
|
204 |
self._nextPageTemplateIndex = self.pageTemplates.index(t)
|
|
205 |
return
|
|
206 |
raise ValueError, "can't find template('%s')"%pt
|
|
207 |
elif type(pt) is IntType:
|
|
208 |
self._nextPageTemplateIndex = pt
|
|
209 |
else:
|
|
210 |
raise TypeError, "argument pt should be string or integer"
|
|
211 |
|
|
212 |
def handle_nextFrame(self,fx):
|
|
213 |
'''On endFrame chenge to the frame with name or index fx'''
|
|
214 |
if type(fx) is StringType:
|
|
215 |
for f in self.pageTemplate.frames:
|
|
216 |
if f.id == fx:
|
|
217 |
self._nextFrameIndex = self.pageTemplate.frames.index(f)
|
|
218 |
return
|
|
219 |
raise ValueError, "can't find frame('%s')"%fx
|
|
220 |
elif type(fx) is IntType:
|
|
221 |
self._nextFrameIndex = fx
|
|
222 |
else:
|
|
223 |
raise TypeError, "argument fx should be string or integer"
|
|
224 |
|
|
225 |
def handle_currentFrame(self,fx):
|
|
226 |
'''chenge to the frame with name or index fx'''
|
|
227 |
if type(fx) is StringType:
|
|
228 |
for f in self.pageTemplate.frames:
|
|
229 |
if f.id == fx:
|
|
230 |
self._nextFrameIndex = self.pageTemplate.frames.index(f)
|
|
231 |
return
|
|
232 |
raise ValueError, "can't find frame('%s')"%fx
|
|
233 |
elif type(fx) is IntType:
|
|
234 |
self._nextFrameIndex = fx
|
|
235 |
else:
|
|
236 |
raise TypeError, "argument fx should be string or integer"
|
|
237 |
|
|
238 |
def handle_flowable(self,flowables):
|
|
239 |
f = flowables[0]
|
|
240 |
del flowables[0]
|
|
241 |
|
|
242 |
if isinstance(f,PageBreak):
|
|
243 |
self.handle_pageBreak()
|
|
244 |
elif isinstance(f,ActionFlowable):
|
|
245 |
f.apply(self)
|
|
246 |
else:
|
|
247 |
#general case we have to do something
|
|
248 |
if not self.frame.add(f, self.canv, trySplit=1):
|
|
249 |
# see if this is a splittable thing
|
|
250 |
S = self.frame.split(f)
|
|
251 |
n = len(S)
|
|
252 |
if n:
|
200
|
253 |
if not self.frame.add(S[0], self.canv, trySplit=0):
|
|
254 |
raise "LayoutError", "splitting error"
|
|
255 |
del S[0]
|
|
256 |
for f in xrange(n-1):
|
197
|
257 |
flowables.insert(f,S[f]) # put split flowables back on the list
|
|
258 |
else:
|
|
259 |
flowables.insert(0,f) # put the flowable back
|
|
260 |
self.handle_frameEnd()
|
|
261 |
|
|
262 |
_handle_documentBegin = handle_documentBegin
|
|
263 |
_handle_pageBegin = handle_pageBegin
|
|
264 |
_handle_pageEnd = handle_pageEnd
|
|
265 |
_handle_frameBegin = handle_frameBegin
|
|
266 |
_handle_frameEnd = handle_frameEnd
|
|
267 |
_handle_flowable = handle_flowable
|
|
268 |
_handle_nextPageTemplate = handle_nextPageTemplate
|
|
269 |
_handle_currentFrame = handle_currentFrame
|
|
270 |
_handle_nextFrame = handle_nextFrame
|
|
271 |
|
|
272 |
def build(self, flowables):
|
|
273 |
assert filter(lambda x: not isinstance(x,Flowable), flowables)==[], "flowables argument error"
|
|
274 |
self.canv = canvas.Canvas(self.filename)
|
|
275 |
self.handle_documentBegin()
|
|
276 |
|
|
277 |
while len(flowables):
|
|
278 |
self.clean_hanging()
|
|
279 |
self.handle_flowable(flowables)
|
|
280 |
|
|
281 |
if self._hanging!=[] and self._hanging[-1] is PageBegin:
|
|
282 |
del self._hanging[-1]
|
|
283 |
self.clean_hanging()
|
|
284 |
else:
|
|
285 |
self.clean_hanging()
|
|
286 |
self.handle_pageBreak()
|
|
287 |
|
|
288 |
self.canv.save()
|
|
289 |
del self.frame, self.pageTemplate
|