Initial try at a document template class
authorrgbecker
Fri, 12 May 2000 12:53:33 +0000
changeset 197 b8ca098f5ec7
parent 196 b2545a941a37
child 198 5604fb99b2e7
Initial try at a document template class
reportlab/platypus/doctemplate.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/reportlab/platypus/doctemplate.py	Fri May 12 12:53:33 2000 +0000
@@ -0,0 +1,285 @@
+###############################################################################
+#
+#	ReportLab Public License Version 1.0
+#
+#   Except for the change of names the spirit and intention of this
+#   license is the same as that of Python
+#
+#	(C) Copyright ReportLab Inc. 1998-2000.
+#
+#
+# All Rights Reserved
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted, provided
+# that the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of ReportLab not be used
+# in advertising or publicity pertaining to distribution of the software
+# without specific, written prior permission. 
+# 
+#
+# Disclaimer
+#
+# ReportLab Inc. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
+# IN NO EVENT SHALL ReportLab BE LIABLE FOR ANY SPECIAL, INDIRECT
+# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE. 
+#
+###############################################################################
+#	$Log: doctemplate.py,v $
+#	Revision 1.1  2000/05/12 12:53:33  rgbecker
+#	Initial try at a document template class
+#
+__version__=''' $Id: doctemplate.py,v 1.1 2000/05/12 12:53:33 rgbecker Exp $ '''
+__doc__="""
+More complicated Document model
+"""
+from layout import *
+from types import *
+import sys
+
+class ActionFlowable(Flowable):
+	'''This Flowable is never drawn, it can be used for data driven controls'''
+	def __init__(self,actions=[]):
+		if type(actions) not in (ListType, TupleType):
+			actions = (actions,)
+		self.actions = actions
+
+	def wrap(self, availWidth, availHeight):
+		raise NotImplementedError
+
+	def draw(self):
+		raise NotImplementedError
+
+	def apply(self,doc):
+		for a in self.actions:
+			if type(a) in (ListType, TupleType):
+				action = a[0]
+				args = tuple(a[1:])
+			else:
+				action = a
+				args = ()
+			try:
+				apply(getattr(doc,'handle_'+action), args)
+			except AttributeError:
+				raise NotImplementedError, "Can't handle ActionFlowable(%s)" % action
+			except:
+				t, v, None = sys.exc_info()
+				raise t, "%s\n   handle_%s args=%s"%(v,action,args)
+				
+
+FrameBreak = ActionFlowable('frameBegin')
+PageBegin = ActionFlowable('pageBegin')
+
+class NextPageTemplate(ActionFlowable):
+	def __init__(self,pt):
+		ActionFlowable.__init__(self,(('nextPageTemplate',pt),))
+
+class PageTemplate:
+	"""
+	essentially a list of BasicFrames and an onPage routine to call at the start
+	of a page when this is selected.
+	"""
+	def __init__(self,id=None,frames=[],onPage=None):
+		if type(frames) not in (ListType,TupleType): frames = [frames]
+		assert filter(lambda x: not isinstance(x,BasicFrame), frames)==[], "frames argument error"
+		self.id = id
+		self.frames = frames
+		self.onPage = onPage or _doNothing
+
+class BaseDocTemplate:
+	"""
+	First attempt at defining a document template class.
+
+	The basic idea is simple.
+	0)	The document has a list of data associated with it
+		this data should derive from flowables. We'll have
+		special classes like PageBreak, FrameBreak to do things
+		like forcing a page end etc.
+
+	1)	The document has one or more page templates.
+
+	2)	Each page template has one or more frames.
+
+	3)	The document class provides base methods for handling the
+		story events and some reasonable methods for getting the
+		story flowables into the frames.
+
+	4)	The document instances can override the base handler routines.
+	"""
+	def __init__(self, filename, pagesize=DEFAULT_PAGE_SIZE, pageTemplates=[], showBoundary=0,
+				leftMargin=inch, rightMargin=inch, topMargin=inch, bottomMargin=inch):
+
+		self.pageTemplates = []
+		self.addPageTemplates(pageTemplates)
+		self.filename = filename
+		self.showBoundary = showBoundary
+		self.leftMargin =  leftMargin
+		self.bottomMargin = bottomMargin
+		self.rightMargin = pagesize[0] - rightMargin
+		self.topMargin = pagesize[1] - topMargin
+		self.width = self.rightMargin - self.leftMargin
+		self.height = self.topMargin - self.bottomMargin
+		self.pagesize = pagesize
+
+	def clean_hanging(self):
+		while len(self._hanging):
+			self.handle_flowable(self._hanging)
+
+	def addPageTemplates(self,pageTemplates):
+		if type(pageTemplates) not in (ListType,TupleType):
+			pageTemplates = [pageTemplates]
+		assert filter(lambda x: not isinstance(x,PageTemplate), pageTemplates)==[], "pageTemplates argument error"
+		for t in pageTemplates:
+			self.pageTemplates.append(t)
+			
+	def handle_documentBegin(self):
+		self._hanging = [PageBegin]
+		self.pageTemplate = self.pageTemplates[0]
+		self.page = 0
+
+	def handle_pageBegin(self):
+		'''shouldn't normally be called directly'''
+		self.page = self.page + 1
+		self.pageTemplate.onPage(self.canv,self)
+		if hasattr(self,'_nextFrameIndex'):
+			del self._nextFrameIndex
+		self.frame = self.pageTemplate.frames[0]
+		self.handle_frameBegin()
+
+	def handle_pageEnd(self):
+		'''	show the current page
+			check the next page template
+			hang a page begin
+		'''
+		self.canv.showPage()
+		if hasattr(self,'_nextPageTemplateIndex'):
+			self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex]
+			del self._nextPageTemplateIndex
+		self._hanging.append(PageBegin)
+
+	def handle_pageBreak(self):
+		'''some might choose not to end all the frames'''
+		if 1:
+			self.handle_pageEnd()
+		else:
+			n = len(self._hanging)
+			while len(self._hanging)==n:
+				self.handle_frameEnd()
+
+	def handle_frameBegin(self,*args):
+		self.frame._reset()
+		if self.showBoundary:
+			self.canv.rect(
+						self.frame.x1,
+						self.frame.y1,
+						self.frame.x2 - self.frame.x1,
+						self.frame.y2 - self.frame.y1
+						)
+
+	def handle_frameEnd(self):
+		'''	Handles the semantics of the end of a frame. This includes the selection of
+			the next frame or if this is the last frame then invoke pageEnd.
+		'''
+		if hasattr(self,'_nextFrameIndex'):
+			frame = self.pageTemplate.frames[self._nextFrameIndex]
+			del self._nextFrameIndex
+		elif hasattr(self.frame,'lastFrame') or self.frame is self.pageTemplate.frames[-1]:
+			self.handle_pageEnd()
+		else:
+			f = self.frame
+			self.frame = self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1]
+			self.handle_frameBegin()
+
+	def handle_nextPageTemplate(self,pt):
+		'''On endPage chenge to the page template with name or index pt'''
+		if type(pt) is StringType:
+			for t in self.pageTemplates:
+				if t.id == pt:
+					self._nextPageTemplateIndex = self.pageTemplates.index(t)
+					return
+			raise ValueError, "can't find template('%s')"%pt
+		elif type(pt) is IntType:
+			self._nextPageTemplateIndex = pt
+		else:
+			raise TypeError, "argument pt should be string or integer"
+
+	def handle_nextFrame(self,fx):
+		'''On endFrame chenge to the frame with name or index fx'''
+		if type(fx) is StringType:
+			for f in self.pageTemplate.frames:
+				if f.id == fx:
+					self._nextFrameIndex = self.pageTemplate.frames.index(f)
+					return
+			raise ValueError, "can't find frame('%s')"%fx
+		elif type(fx) is IntType:
+			self._nextFrameIndex = fx
+		else:
+			raise TypeError, "argument fx should be string or integer"
+
+	def handle_currentFrame(self,fx):
+		'''chenge to the frame with name or index fx'''
+		if type(fx) is StringType:
+			for f in self.pageTemplate.frames:
+				if f.id == fx:
+					self._nextFrameIndex = self.pageTemplate.frames.index(f)
+					return
+			raise ValueError, "can't find frame('%s')"%fx
+		elif type(fx) is IntType:
+			self._nextFrameIndex = fx
+		else:
+			raise TypeError, "argument fx should be string or integer"
+
+	def handle_flowable(self,flowables):
+		f = flowables[0]
+		del flowables[0]
+
+		if isinstance(f,PageBreak):
+			self.handle_pageBreak()
+		elif isinstance(f,ActionFlowable):
+			f.apply(self)
+		else:
+			#general case we have to do something
+			if not self.frame.add(f, self.canv, trySplit=1):
+				# see if this is a splittable thing
+				S = self.frame.split(f)
+				n = len(S)
+				if n:
+					for f in xrange(n):
+						flowables.insert(f,S[f])	# put split flowables back on the list
+				else:
+					flowables.insert(0,f)			# put the flowable back
+					self.handle_frameEnd()
+
+	_handle_documentBegin = handle_documentBegin
+	_handle_pageBegin = handle_pageBegin
+	_handle_pageEnd = handle_pageEnd
+	_handle_frameBegin = handle_frameBegin
+	_handle_frameEnd = handle_frameEnd
+	_handle_flowable = handle_flowable
+	_handle_nextPageTemplate = handle_nextPageTemplate
+	_handle_currentFrame = handle_currentFrame
+	_handle_nextFrame = handle_nextFrame
+
+	def build(self, flowables):
+		assert filter(lambda x: not isinstance(x,Flowable), flowables)==[], "flowables argument error"
+		self.canv = canvas.Canvas(self.filename)
+		self.handle_documentBegin()
+
+		while len(flowables):
+			self.clean_hanging()
+			self.handle_flowable(flowables)
+
+		if self._hanging!=[] and self._hanging[-1] is PageBegin:
+			del self._hanging[-1]
+			self.clean_hanging()
+		else:
+			self.clean_hanging()
+			self.handle_pageBreak()
+
+		self.canv.save()
+		del self.frame, self.pageTemplate