reportlab/platypus/doctemplate.py
author rgbecker
Fri, 12 May 2000 12:53:33 +0000
changeset 197 b8ca098f5ec7
child 199 7de4e498ce32
permissions -rw-r--r--
Initial try at a document template class

###############################################################################
#
#	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