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