Fixes/Changes to get testplatypus to work with new framework
authorrgbecker
Tue, 16 May 2000 14:28:55 +0000
changeset 218 274db2129c04
parent 217 8d8a980841a8
child 219 d0ab76dc033f
Fixes/Changes to get testplatypus to work with new framework
reportlab/platypus/doctemplate.py
reportlab/platypus/paragraph.py
reportlab/platypus/paraparser.py
reportlab/platypus/test/testplatypus.py
--- a/reportlab/platypus/doctemplate.py	Tue May 16 11:18:10 2000 +0000
+++ b/reportlab/platypus/doctemplate.py	Tue May 16 14:28:55 2000 +0000
@@ -31,9 +31,12 @@
 #
 ###############################################################################
 #	$Log: doctemplate.py,v $
+#	Revision 1.7  2000/05/16 14:28:55  rgbecker
+#	Fixes/Changes to get testplatypus to work with new framework
+#
 #	Revision 1.6  2000/05/15 15:07:32  rgbecker
 #	Added drawPage
-#
+#	
 #	Revision 1.5  2000/05/13 08:33:53  rgbecker
 #	fix typo in import
 #	
@@ -49,7 +52,7 @@
 #	Revision 1.1  2000/05/12 12:53:33  rgbecker
 #	Initial try at a document template class
 #	
-__version__=''' $Id: doctemplate.py,v 1.6 2000/05/15 15:07:32 rgbecker Exp $ '''
+__version__=''' $Id: doctemplate.py,v 1.7 2000/05/16 14:28:55 rgbecker Exp $ '''
 __doc__="""
 More complicated Document model
 """
@@ -82,7 +85,10 @@
 			t, v, None = sys.exc_info()
 			raise t, "%s\n   handle_%s args=%s"%(v,action,args)
 
-FrameBreak = ActionFlowable('frameBegin')
+	def __call__(self):
+		return self
+
+FrameBreak = ActionFlowable('frameEnd')
 PageBegin = ActionFlowable('pageBegin')
 
 class NextPageTemplate(ActionFlowable):
@@ -129,7 +135,8 @@
 	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):
+				leftMargin=inch, rightMargin=inch, topMargin=inch, bottomMargin=inch,
+				allowSplitting=1):
 
 		self.pageTemplates = []
 		self.addPageTemplates(pageTemplates)
@@ -142,6 +149,8 @@
 		self.width = self.rightMargin - self.leftMargin
 		self.height = self.topMargin - self.bottomMargin
 		self.pagesize = pagesize
+		self.allowSplitting = allowSplitting
+		self._pageBreakQuick = 1
 
 	def clean_hanging(self):
 		while len(self._hanging):
@@ -182,7 +191,7 @@
 
 	def handle_pageBreak(self):
 		'''some might choose not to end all the frames'''
-		if 1:
+		if self._pageBreakQuick:
 			self.handle_pageEnd()
 		else:
 			n = len(self._hanging)
@@ -191,7 +200,7 @@
 
 	def handle_frameBegin(self,*args):
 		self.frame._reset()
-		if self.showBoundary:
+		if self.showBoundary or self.frame.showBoundary:
 			self.canv.rect(
 						self.frame.x1,
 						self.frame.y1,
@@ -209,6 +218,7 @@
 			self.handle_frameBegin()
 		elif hasattr(self.frame,'lastFrame') or self.frame is self.pageTemplate.frames[-1]:
 			self.handle_pageEnd()
+			self.frame = None
 		else:
 			f = self.frame
 			self.frame = self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1]
@@ -254,6 +264,7 @@
 			raise TypeError, "argument fx should be string or integer"
 
 	def handle_flowable(self,flowables):
+		'''try to handle one flowable from the front of list flowables.'''
 		f = flowables[0]
 		del flowables[0]
 
@@ -263,10 +274,14 @@
 			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 not self.frame.add(f, self.canv, trySplit=self.allowSplitting):
+				if self.allowSplitting:
+					# see if this is a splittable thing
+					S = self.frame.split(f)
+					n = len(S)
+				else:
+					n = 0
+
 				if n:
 					if not self.frame.add(S[0], self.canv, trySplit=0):
 						raise "LayoutError", "splitting error"
@@ -288,15 +303,11 @@
 	_handle_currentFrame = handle_currentFrame
 	_handle_nextFrame = handle_nextFrame
 
-	def build(self, flowables):
-		assert filter(lambda x: not isinstance(x,Flowable), flowables)==[], "flowables argument error"
+	def _startBuild(self):
 		self.canv = canvas.Canvas(self.filename)
 		self.handle_documentBegin()
 
-		while len(flowables):
-			self.clean_hanging()
-			self.handle_flowable(flowables)
-
+	def _endBuild(self):
 		if self._hanging!=[] and self._hanging[-1] is PageBegin:
 			del self._hanging[-1]
 			self.clean_hanging()
@@ -306,3 +317,13 @@
 
 		self.canv.save()
 		del self.frame, self.pageTemplate
+
+	def build(self, flowables):
+		assert filter(lambda x: not isinstance(x,Flowable), flowables)==[], "flowables argument error"
+		self._startBuild()
+
+		while len(flowables):
+			self.clean_hanging()
+			self.handle_flowable(flowables)
+
+		self._endBuild()
--- a/reportlab/platypus/paragraph.py	Tue May 16 11:18:10 2000 +0000
+++ b/reportlab/platypus/paragraph.py	Tue May 16 14:28:55 2000 +0000
@@ -31,9 +31,12 @@
 #
 ###############################################################################
 #	$Log: paragraph.py,v $
+#	Revision 1.7  2000/05/16 14:28:55  rgbecker
+#	Fixes/Changes to get testplatypus to work with new framework
+#
 #	Revision 1.6  2000/05/15 13:36:11  rgbecker
 #	Splitting changes
-#
+#	
 #	Revision 1.5  2000/05/13 16:03:23  rgbecker
 #	Fix extraspace calculation
 #	
@@ -49,7 +52,7 @@
 #	Revision 1.1  2000/04/14 13:21:52  rgbecker
 #	Removed from layout.py
 #	
-__version__=''' $Id: paragraph.py,v 1.6 2000/05/15 13:36:11 rgbecker Exp $ '''
+__version__=''' $Id: paragraph.py,v 1.7 2000/05/16 14:28:55 rgbecker Exp $ '''
 import string
 import types
 from reportlab.pdfbase.pdfmetrics import stringWidth
@@ -450,7 +453,6 @@
 			canvas.saveState()
 			canvas.addLiteral('% textcanvas.drawParagraph()')
 			alignment = style.alignment
-			#is there a bullet?  if so, draw it first
 			offset = style.firstLineIndent - style.leftIndent
 			lim = nLines-1
 			noJustifyLast = not (hasattr(self,'_JustifyLast') and self._JustifyLast)
@@ -470,6 +472,7 @@
 				if self.bulletText <> None:
 					tx2 = canvas.beginText(style.bulletIndent, cur_y)
 					tx2.setFont(style.bulletFontName, style.bulletFontSize)
+					tx2.setFillColor(hasattr(style,'bulletColor') and style.bulletColor or style.textColor)
 					tx2.textOut(self.bulletText)
 					bulletEnd = tx2.getX()
 					offset = max(offset, bulletEnd - style.leftIndent)
--- a/reportlab/platypus/paraparser.py	Tue May 16 11:18:10 2000 +0000
+++ b/reportlab/platypus/paraparser.py	Tue May 16 14:28:55 2000 +0000
@@ -32,9 +32,12 @@
 #
 ###############################################################################
 #	$Log: paraparser.py,v $
+#	Revision 1.17  2000/05/16 14:28:55  rgbecker
+#	Fixes/Changes to get testplatypus to work with new framework
+#
 #	Revision 1.16  2000/05/15 12:15:29  rgbecker
 #	CDATA handler added
-#
+#	
 #	Revision 1.15  2000/05/13 16:04:06  rgbecker
 #	made size alias of fontsize for <para>
 #	
@@ -44,7 +47,7 @@
 #	Revision 1.13  2000/04/25 13:07:57  rgbecker
 #	Added license
 #	
-__version__=''' $Id: paraparser.py,v 1.16 2000/05/15 12:15:29 rgbecker Exp $ '''
+__version__=''' $Id: paraparser.py,v 1.17 2000/05/16 14:28:55 rgbecker Exp $ '''
 import string
 import re
 from types import TupleType
@@ -106,6 +109,7 @@
 				'bfont': ('bulletFontName', None),
 				'bfontsize': ('bulletFontIndent',_num),
 				'bindent': ('bulletFontIndent',_num),
+				'bcolor': ('bulletColor',stringToColor),
 				'color':('textColor',stringToColor),
 				'fg': ('textColor',stringToColor)}
 
--- a/reportlab/platypus/test/testplatypus.py	Tue May 16 11:18:10 2000 +0000
+++ b/reportlab/platypus/test/testplatypus.py	Tue May 16 14:28:55 2000 +0000
@@ -1,10 +1,9 @@
-#!/bin/env python
 ###############################################################################
 #
 #	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
+#	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.
 #
@@ -17,8 +16,8 @@
 # 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. 
-# 
+# without specific, written prior permission.
+#
 #
 # Disclaimer
 #
@@ -28,14 +27,17 @@
 # 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. 
+# PERFORMANCE OF THIS SOFTWARE.
 #
 ###############################################################################
 #	$Log: testplatypus.py,v $
+#	Revision 1.15  2000/05/16 14:28:55  rgbecker
+#	Fixes/Changes to get testplatypus to work with new framework
+#
 #	Revision 1.14  2000/04/26 11:07:15  andy_robinson
 #	Tables changed to use reportlab.lib.colors instead of
 #	the six hard-coded color strings there previously.
-#
+#	
 #	Revision 1.13  2000/04/14 12:42:35  rgbecker
 #	Minor textual and spacing adjustments
 #	
@@ -72,13 +74,13 @@
 #	Revision 1.2  2000/02/15 15:47:10  rgbecker
 #	Added license, __version__ and Logi comment
 #	
-__version__=''' $Id: testplatypus.py,v 1.14 2000/04/26 11:07:15 andy_robinson Exp $ '''
+__version__=''' $Id: testplatypus.py,v 1.15 2000/05/16 14:28:55 rgbecker Exp $ '''
 
 #tests and documents Page Layout API
 __doc__="""This is not obvious so here's a brief explanation.  This module is both
-the test script and user guide for layout.  Each page has two frames on it:
+the test script and user guide for layout.	Each page has two frames on it:
 one for commentary, and one for demonstration objects which may be drawn in
-various esoteric ways.  The two functions getCommentary() and getExamples()
+various esoteric ways.	The two functions getCommentary() and getExamples()
 return the 'story' for each.  The run() function gets the stories, then
 builds a special "document model" in which the frames are added to each page
 and drawn into.
@@ -86,6 +88,7 @@
 import string, copy
 from reportlab.pdfgen import canvas
 from reportlab.platypus import layout, tables
+from reportlab.platypus.doctemplate import BaseDocTemplate, PageTemplate, Flowable, FrameBreak
 from reportlab.platypus.paragraph import Paragraph
 from reportlab.lib.units import inch, cm
 from reportlab.lib.styles import PropertySet, getSampleStyleSheet, ParagraphStyle
@@ -100,418 +103,402 @@
 
 BASEFONT = ('Times-Roman', 10)
 
-def framePage(canvas):
-    #canvas.drawImage("snkanim.gif", 36, 36)
-    canvas.saveState()
-    canvas.setStrokeColorRGB(1,0,0)
-    canvas.setLineWidth(5)
-    canvas.line(66,72,66,layout.PAGE_HEIGHT-72)
+def framePage(canvas,doc):
+	#canvas.drawImage("snkanim.gif", 36, 36)
+	canvas.saveState()
+	canvas.setStrokeColorRGB(1,0,0)
+	canvas.setLineWidth(5)
+	canvas.line(66,72,66,layout.PAGE_HEIGHT-72)
 
-    canvas.setFont('Times-Italic',12)
-    canvas.drawRightString(523, layout.PAGE_HEIGHT - 56, "Platypus User Guide and Test Script")
-    
-    canvas.setFont('Times-Roman',12)
-    canvas.drawString(4 * inch, 0.75 * inch,
-                        "Page %d" % canvas.getPageNumber())
-    canvas.restoreState()
-    
+	canvas.setFont('Times-Italic',12)
+	canvas.drawRightString(523, layout.PAGE_HEIGHT - 56, "Platypus User Guide and Test Script")
+	
+	canvas.setFont('Times-Roman',12)
+	canvas.drawString(4 * inch, 0.75 * inch,
+						"Page %d" % canvas.getPageNumber())
+	canvas.restoreState()
+	
 
 def getParagraphs(textBlock):
-    """Within the script, it is useful to whack out a page in triple
-    quotes containign separate paragraphs. This breaks one into its
-    constituent paragraphs, using blank lines as the delimiter."""
-    lines = string.split(textBlock, '\n')
-    paras = []
-    currentPara = []
-    for line in lines:
-        if len(string.strip(line)) == 0:
-            #blank, add it
-            if currentPara <> []:
-                paras.append(string.join(currentPara, '\n'))
-                currentPara = []
-        else:
-            currentPara.append(line)
-    #...and the last one
-    if currentPara <> []:
-        paras.append(string.join(currentPara, '\n'))
+	"""Within the script, it is useful to whack out a page in triple
+	quotes containing separate paragraphs. This breaks one into its
+	constituent paragraphs, using blank lines as the delimiter."""
+	lines = string.split(textBlock, '\n')
+	paras = []
+	currentPara = []
+	for line in lines:
+		if len(string.strip(line)) == 0:
+			#blank, add it
+			if currentPara <> []:
+				paras.append(string.join(currentPara, '\n'))
+				currentPara = []
+		else:
+			currentPara.append(line)
+	#...and the last one
+	if currentPara <> []:
+		paras.append(string.join(currentPara, '\n'))
 
-    return paras
+	return paras
 
 def getCommentary():
-    """Returns the story for the commentary - all the paragraphs."""
+	"""Returns the story for the commentary - all the paragraphs."""
 
-    styleSheet = getSampleStyleSheet()
-    
-    story = []
-    story.append(Paragraph("""
-        PLATYPUS User Guide and Test Script
-        """, styleSheet['Heading1']))
+	styleSheet = getSampleStyleSheet()
+	
+	story = []
+	story.append(Paragraph("""
+		PLATYPUS User Guide and Test Script
+		""", styleSheet['Heading1']))
 
 
-    spam = """
-    Welcome to PLATYPUS!
+	spam = """
+	Welcome to PLATYPUS!
 
-    Platypus stands for "Page Layout and Typography Using Scripts".  It is a high
-    level page layout library which lets you programmatically create complex
-    documents with a minimum of effort.
+	Platypus stands for "Page Layout and Typography Using Scripts".  It is a high
+	level page layout library which lets you programmatically create complex
+	documents with a minimum of effort.
 
-    This document is both the user guide and the output of the test script.
-    In other words, a script used platypus to create the document you are now
-    reading, and the fact that you are reading it proves that it works.  Or
-    rather, that it worked for this script anyway.  It is a first release!  
+	This document is both the user guide and the output of the test script.
+	In other words, a script used platypus to create the document you are now
+	reading, and the fact that you are reading it proves that it works.  Or
+	rather, that it worked for this script anyway.	It is a first release!	
 
-    Platypus is built 'on top of' PDFgen, the Python library for creating PDF
-    documents.  To learn about PDFgen, read the document testpdfgen.pdf.
+	Platypus is built 'on top of' PDFgen, the Python library for creating PDF
+	documents.	To learn about PDFgen, read the document testpdfgen.pdf.
 
-    """    
+	"""
 
-    for text in getParagraphs(spam):
-        story.append(Paragraph(text, styleSheet['BodyText']))
+	for text in getParagraphs(spam):
+		story.append(Paragraph(text, styleSheet['BodyText']))
 
-    story.append(Paragraph("""
-        What concepts does PLATYPUS deal with?
-        """, styleSheet['Heading2']))
-    story.append(Paragraph("""
-        The central concepts in PLATYPUS are Flowable Objects, Frames, Flow
-        Management, Styles and Style Sheets, Paragraphs and Tables.  This is
-        best explained in contrast to PDFgen, the layer underneath PLATYPUS.
-        PDFgen is a graphics library, and has primitive commans to draw lines
-        and strings.  There is nothing in it to manage the flow of text down
-        the page.  PLATYPUS works at the conceptual level fo a desktop publishing
-        package; you can write programs which deal intelligently with graphic
-        objects and fit them onto the page.
-        """, styleSheet['BodyText']))
+	story.append(Paragraph("""
+		What concepts does PLATYPUS deal with?
+		""", styleSheet['Heading2']))
+	story.append(Paragraph("""
+		The central concepts in PLATYPUS are Flowable Objects, Frames, Flow
+		Management, Styles and Style Sheets, Paragraphs and Tables.  This is
+		best explained in contrast to PDFgen, the layer underneath PLATYPUS.
+		PDFgen is a graphics library, and has primitive commans to draw lines
+		and strings.  There is nothing in it to manage the flow of text down
+		the page.  PLATYPUS works at the conceptual level fo a desktop publishing
+		package; you can write programs which deal intelligently with graphic
+		objects and fit them onto the page.
+		""", styleSheet['BodyText']))
 
-    story.append(Paragraph("""
-        How is this document organized?
-        """, styleSheet['Heading2']))
+	story.append(Paragraph("""
+		How is this document organized?
+		""", styleSheet['Heading2']))
 
-    story.append(Paragraph("""
-        Since this is a test script, we'll just note how it is organized.
-        the top of each page contains commentary.  The bottom half contains
-        example drawings and graphic elements to whicht he commentary will
-        relate.  Down below, you can see the outline of a text frame, and
-        various bits and pieces within it.  We'll explain how they work
-        on the next page.
-        """, styleSheet['BodyText']))
+	story.append(Paragraph("""
+		Since this is a test script, we'll just note how it is organized.
+		the top of each page contains commentary.  The bottom half contains
+		example drawings and graphic elements to whicht he commentary will
+		relate.  Down below, you can see the outline of a text frame, and
+		various bits and pieces within it.	We'll explain how they work
+		on the next page.
+		""", styleSheet['BodyText']))
 
-    story.append(layout.PageBreak())
-    #######################################################################
-    #     Commentary Page 2
-    #######################################################################
-    
-    story.append(Paragraph("""
-        Flowable Objects
-        """, styleSheet['Heading2']))
-    spam = """
-        The first and most fundamental concept is that of a 'Flowable Object'.
-        In PDFgen, you draw stuff by calling methods of the canvas to set up
-        the colors, fonts and line styles, and draw the graphics primitives.
-        If you set the pen color to blue, everything you draw after will be
-        blue until you change it again.  And you have to handle all of the X-Y
-        coordinates yourself.
+	story.append(FrameBreak())
+	#######################################################################
+	#	  Commentary Page 2
+	#######################################################################
+	
+	story.append(Paragraph("""
+		Flowable Objects
+		""", styleSheet['Heading2']))
+	spam = """
+		The first and most fundamental concept is that of a 'Flowable Object'.
+		In PDFgen, you draw stuff by calling methods of the canvas to set up
+		the colors, fonts and line styles, and draw the graphics primitives.
+		If you set the pen color to blue, everything you draw after will be
+		blue until you change it again.  And you have to handle all of the X-Y
+		coordinates yourself.
 
-        A 'Flowable object' is exactly what it says.  It knows how to draw itself
-        on the canvas, and the way it does so is totally independent of what
-        you drew before or after.  Furthermore, it draws itself at the location
-        on the page you specify.
+		A 'Flowable object' is exactly what it says.  It knows how to draw itself
+		on the canvas, and the way it does so is totally independent of what
+		you drew before or after.  Furthermore, it draws itself at the location
+		on the page you specify.
 
-        The most fundamental Flowable Objects in most documents are likely to be
-        paragraphs, tables, diagrams/charts and images - but there is no
-        restriction.  You can write your own easily, and I hope that people
-        will start to contribute them.  PINGO users - we provide a "PINGO flowable" object to let
-        you insert platform-independent graphics into the flow of a document.
+		The most fundamental Flowable Objects in most documents are likely to be
+		paragraphs, tables, diagrams/charts and images - but there is no
+		restriction.  You can write your own easily, and I hope that people
+		will start to contribute them.	PINGO users - we provide a "PINGO flowable" object to let
+		you insert platform-independent graphics into the flow of a document.
 
-        When you write a flowable object, you inherit from Flowable and
-        must implement two methods.  object.wrap(availWidth, availHeight) will be called by other parts of
-        the system, and tells you how much space you have.  You should return
-        how much space you are going to use.  For a fixed-size object, this
-        is trivial, but it is critical - PLATYPUS needs to figure out if things
-        will fit on the page before drawing them.  For other objects such as paragraphs,
-        the height is obviously determined by the available width.
+		When you write a flowable object, you inherit from Flowable and
+		must implement two methods.  object.wrap(availWidth, availHeight) will be called by other parts of
+		the system, and tells you how much space you have.	You should return
+		how much space you are going to use.  For a fixed-size object, this
+		is trivial, but it is critical - PLATYPUS needs to figure out if things
+		will fit on the page before drawing them.  For other objects such as paragraphs,
+		the height is obviously determined by the available width.
 
 
-        The second method is object.draw().  Here, you do whatever you want.
-        The Flowable base class sets things up so that you have an origin of
-        (0,0) for your drawing, and everything will fit nicely if you got the
-        height and width right.  It also saves and restores the graphics state
-        around your calls, so you don;t have to reset all the properties you
-        changed.
-
-        Programs which actually draw a Flowable don't
-        call draw() this directly - they call object.drawOn(canvas, x, y).
-        So you can write code in your own coordinate system, and things
-        can be drawn anywhere on the page (possibly even scaled or rotated).
-        """    
-    for text in getParagraphs(spam):
-        story.append(Paragraph(text, styleSheet['BodyText']))
+		The second method is object.draw().  Here, you do whatever you want.
+		The Flowable base class sets things up so that you have an origin of
+		(0,0) for your drawing, and everything will fit nicely if you got the
+		height and width right.  It also saves and restores the graphics state
+		around your calls, so you don;t have to reset all the properties you
+		changed.
 
-    story.append(layout.PageBreak())
-    #######################################################################
-    #     Commentary Page 3
-    #######################################################################
+		Programs which actually draw a Flowable don't
+		call draw() this directly - they call object.drawOn(canvas, x, y).
+		So you can write code in your own coordinate system, and things
+		can be drawn anywhere on the page (possibly even scaled or rotated).
+		"""
+	for text in getParagraphs(spam):
+		story.append(Paragraph(text, styleSheet['BodyText']))
 
-    story.append(Paragraph("""
-        Available Flowable Objects
-        """, styleSheet['Heading2']))
+	story.append(FrameBreak())
+	#######################################################################
+	#	  Commentary Page 3
+	#######################################################################
 
-##    spam = """
-##
-##        """    
-##    for text in getParagraphs(spam):
-##        story.append(Paragraph(text, styleSheet['BodyText']))
+	story.append(Paragraph("""
+		Available Flowable Objects
+		""", styleSheet['Heading2']))
 
-    story.append(Paragraph("""
-        Platypus comes with a basic set of flowable objects.  Here we list their
-        class names and tell you what they do:
-        """, styleSheet['BodyText']))
-    #we can use the bullet feature to do a definition list
-    story.append(Paragraph("""
-        This is a contrived object to give an example of a Flowable -
-        just a fixed-size box with an X through it and a centred string.""",
-            styleSheet['Definition'],
-            bulletText='XBox  '  #hack - spot the extra space after
-            ))
-        
-    story.append(Paragraph("""
-        This is the basic unit of a document.  Paragraphs can be finely
-        tuned and offer a host of properties through their associated
-        ParagraphStyle.""",
-            styleSheet['Definition'],
-            bulletText='Paragraph  '  #hack - spot the extra space after
-            ))
-    
-    story.append(Paragraph("""
-        This is used for printing code and other preformatted text.
-        There is no wrapping, and line breaks are taken where they occur.
-        Many paragraph style properties do not apply.  You may supply
-        an optional 'dedent' parameter to trim a number of characters
-        off the front of each line.""",
-            styleSheet['Definition'],
-            bulletText='Preformatted  '  #hack - spot the extra space after
-            ))
-    story.append(Paragraph("""
-        This is a straight wrapper around an external image file.  By default
-        the image will be drawn at a scale of one pixel equals one point, and
-        centred in the frame.  You may supply an optional width and height.""",
-            styleSheet['Definition'],
-            bulletText='Image  '  #hack - spot the extra space after
-            ))
-        
-    story.append(Paragraph("""
-        This is a base class for making grids.  It is really just a base for
-        TextGrid below.""",
-            styleSheet['Definition'],
-            bulletText='BaseGrid  '  #hack - spot the extra space after
-            ))
+	story.append(Paragraph("""
+		Platypus comes with a basic set of flowable objects.  Here we list their
+		class names and tell you what they do:
+		""", styleSheet['BodyText']))
+	#we can use the bullet feature to do a definition list
+	story.append(Paragraph("""
+		<para color=green bcolor=red>This is a contrived object to give an example of a Flowable -
+		just a fixed-size box with an X through it and a centred string.</para>""",
+			styleSheet['Definition'],
+			bulletText='XBox  '  #hack - spot the extra space after
+			))
+		
+	story.append(Paragraph("""
+		This is the basic unit of a document.  Paragraphs can be finely
+		tuned and offer a host of properties through their associated
+		ParagraphStyle.""",
+			styleSheet['Definition'],
+			bulletText='Paragraph  '  #hack - spot the extra space after
+			))
+	
+	story.append(Paragraph("""
+		This is used for printing code and other preformatted text.
+		There is no wrapping, and line breaks are taken where they occur.
+		Many paragraph style properties do not apply.  You may supply
+		an optional 'dedent' parameter to trim a number of characters
+		off the front of each line.""",
+			styleSheet['Definition'],
+			bulletText='Preformatted  '  #hack - spot the extra space after
+			))
+	story.append(Paragraph("""
+		This is a straight wrapper around an external image file.  By default
+		the image will be drawn at a scale of one pixel equals one point, and
+		centred in the frame.  You may supply an optional width and height.""",
+			styleSheet['Definition'],
+			bulletText='Image  '  #hack - spot the extra space after
+			))
+		
+	story.append(Paragraph("""
+		This is a table drawing class; it is intended to be simpler
+		than a full HTML table model yet be able to draw attractive output,
+		and behave intelligently when the numbers of rows and columns vary.
+		Still need to add the cell properties (shading, alignment, font etc.)""",
+			styleSheet['Definition'],
+			bulletText='Table  '  #hack - spot the extra space after
+			))
 
-    story.append(Paragraph("""
-        This is a table drawing class descended from BaseGrid.  It is intended to be simpler
-        than a full HTML table model yet be able to draw attractive output,
-        and behave intelligently when the numbers of rows and columns vary.
-        Still need to add the cell properties (shading, alignment, font etc.)""",
-            styleSheet['Definition'],
-            bulletText='TextGrid  '  #hack - spot the extra space after
-            ))
-
-    story.append(Paragraph("""
-        This is a 'null object' which merely takes up space on the page.
-        Use it when you want some extra padding betweene elements.""",
-            styleSheet['Definition'],
-            bulletText='Spacer  '  #hack - spot the extra space after
-            ))
+	story.append(Paragraph("""
+		This is a 'null object' which merely takes up space on the page.
+		Use it when you want some extra padding betweene elements.""",
+			styleSheet['Definition'],
+			bulletText='Spacer '  #hack - spot the extra space after
+			))
 
-    story.append(Paragraph("""
-        A PageBreak consumes all the remaining space in a frame.""",
-            styleSheet['Definition'],
-            bulletText='PageBreak  '  #hack - spot the extra space after
-            ))
+	story.append(Paragraph("""
+		A FrameBreak causes the document to call its handle_frameEnd method.""",
+			styleSheet['Definition'],
+			bulletText='FrameBreak  '  #hack - spot the extra space after
+			))
 
-    story.append(Paragraph("""
-        This is in progress, but a macro is basically a chunk of Python code to
-        be evaluated when it is drawn.  It could do lots of neat things.""",
-            styleSheet['Definition'],
-            bulletText='Macro  '  #hack - spot the extra space after
-            ))
+	story.append(Paragraph("""
+		This is in progress, but a macro is basically a chunk of Python code to
+		be evaluated when it is drawn.	It could do lots of neat things.""",
+			styleSheet['Definition'],
+			bulletText='Macro  '  #hack - spot the extra space after
+			))
 
-
-    return story
+	story.append(FrameBreak())
+	return story
 
 def getExamples():
-    """Returns all the example flowable objects"""
-    styleSheet = getSampleStyleSheet()
-    
-    story = []
+	"""Returns all the example flowable objects"""
+	styleSheet = getSampleStyleSheet()
+	
+	story = []
 
-    #make a style with indents and spacing
-    sty = ParagraphStyle('obvious', None)
-    sty.leftIndent = 18
-    sty.rightIndent = 18
-    sty.firstLineIndent = 36
-    sty.spaceBefore = 6
-    sty.spaceAfter = 6
-    story.append(Paragraph("""Now for some demo stuff - we need some on this page,
-        even before we explain the concepts fully""", styleSheet['BodyText']))
-    p = Paragraph("""
-        Platypus is all about fitting objects into frames on the page.  You
-        are looking at a fairly simple Platypus paragraph in Debug mode.
-        It has some gridlines drawn around it to show the left and right indents,
-        and the space before and after, all of which are attributes set in
-        the style sheet.  To be specific, this paragraph has left and
-        right indents of 18 points, a first line indent of 36 points,
-        and 6 points of space before and after itself.  A paragraph
-        object fills the width of the enclosing frame, as you would expect.""", sty)
+	#make a style with indents and spacing
+	sty = ParagraphStyle('obvious', None)
+	sty.leftIndent = 18
+	sty.rightIndent = 18
+	sty.firstLineIndent = 36
+	sty.spaceBefore = 6
+	sty.spaceAfter = 6
+	story.append(Paragraph("""Now for some demo stuff - we need some on this page,
+		even before we explain the concepts fully""", styleSheet['BodyText']))
+	p = Paragraph("""
+		Platypus is all about fitting objects into frames on the page.	You
+		are looking at a fairly simple Platypus paragraph in Debug mode.
+		It has some gridlines drawn around it to show the left and right indents,
+		and the space before and after, all of which are attributes set in
+		the style sheet.  To be specific, this paragraph has left and
+		right indents of 18 points, a first line indent of 36 points,
+		and 6 points of space before and after itself.	A paragraph
+		object fills the width of the enclosing frame, as you would expect.""", sty)
 
-    p.debug = 1   #show me the borders
-    story.append(p)
+	p.debug = 1   #show me the borders
+	story.append(p)
+
+	story.append(Paragraph("""Same but with justification 1.5 extra leading and green text.""", styleSheet['BodyText']))
+	p = Paragraph("""
+		<para align=justify leading=+1.5 fg=green><font color=red>Platypus</font> is all about fitting objects into frames on the page.  You
+		are looking at a fairly simple Platypus paragraph in Debug mode.
+		It has some gridlines drawn around it to show the left and right indents,
+		and the space before and after, all of which are attributes set in
+		the style sheet.  To be specific, this paragraph has left and
+		right indents of 18 points, a first line indent of 36 points,
+		and 6 points of space before and after itself.	A paragraph
+		object fills the width of the enclosing frame, as you would expect.</para>""", sty)
 
-    story.append(Paragraph("""Same but with justification 1.5 extra leading and green text.""", styleSheet['BodyText']))
-    p = Paragraph("""
-        <para align=justify leading=+1.5 fg=green><font color=red>Platypus</font> is all about fitting objects into frames on the page.  You
-        are looking at a fairly simple Platypus paragraph in Debug mode.
-        It has some gridlines drawn around it to show the left and right indents,
-        and the space before and after, all of which are attributes set in
-        the style sheet.  To be specific, this paragraph has left and
-        right indents of 18 points, a first line indent of 36 points,
-        and 6 points of space before and after itself.  A paragraph
-        object fills the width of the enclosing frame, as you would expect.</para>""", sty)
+	p.debug = 1   #show me the borders
+	story.append(p)
+
+	story.append(layout.XBox(4*inch, 0.75*inch,
+			'This is a box with a fixed size'))
+
+	story.append(Paragraph("""
+		All of this is being drawn within a text frame which was defined
+		on the page.  This frame is in 'debug' mode so you can see the border,
+		and also see the margins which it reserves.  A frame does not have
+		to have margins, but they have been set to 6 points each to create
+		a little space around the contents.
+		""", styleSheet['BodyText']))
+
+	story.append(FrameBreak())
 
-    p.debug = 1   #show me the borders
-    story.append(p)
-
-    story.append(layout.XBox(4*inch, 0.75*inch,
-            'This is a box with a fixed size'))
+	#######################################################################
+	#	  Examples Page 2
+	#######################################################################
+	
+	story.append(Paragraph("""
+		Here's the base class for Flowable...
+		""", styleSheet['Italic']))
+	
+	code = '''class Flowable:
+		"""Abstract base class for things to be drawn.	Key concepts:
+	1. It knows its size
+	2. It draws in its own coordinate system (this requires the
+		base API to provide a translate() function.
+		"""
+	def __init__(self):
+		self.width = 0
+		self.height = 0
+		self.wrapped = 0
+		
+	def drawOn(self, canvas, x, y):
+		"Tell it to draw itself on the canvas.	Do not override"
+		self.canv = canvas
+		self.canv.saveState()
+		self.canv.translate(x, y)
 
-    story.append(Paragraph("""
-        All of this is being drawn within a text frame which was defined
-        on the page.  This frame is in 'debug' mode so you can see the border,
-        and also see the margins which it reserves.  A frame does not have
-        to have margins, but they have been set to 6 points each to create
-        a little space around the contents.
-        """, styleSheet['BodyText']))
-
-    story.append(layout.PageBreak())
+		self.draw()   #this is the bit you overload
 
-    #######################################################################
-    #     Examples Page 2
-    #######################################################################
-    
-    story.append(Paragraph("""
-        Here's the base class for Flowable...
-        """, styleSheet['Italic']))
-    
-    code = '''class Flowable:
-        """Abstract base class for things to be drawn.  Key concepts:
-    1. It knows its size
-    2. It draws in its own coordinate system (this requires the
-        base API to provide a translate() function.
-        """
-    def __init__(self):
-        self.width = 0
-        self.height = 0
-        self.wrapped = 0
-        
-    def drawOn(self, canvas, x, y):
-        "Tell it to draw itself on the canvas.  Do not override"
-        self.canv = canvas
-        self.canv.saveState()
-        self.canv.translate(x, y)
+		self.canv.restoreState()
+		del self.canv
+		
+	def wrap(self, availWidth, availHeight):
+		"""This will be called by the enclosing frame before objects
+		are asked their size, drawn or whatever.  It returns the
+		size actually used."""
+		return (self.width, self.height)
+	'''
+	
+	story.append(layout.Preformatted(code, styleSheet['Code'], dedent=4))
+	story.append(FrameBreak())
+	#######################################################################
+	#	  Examples Page 3
+	#######################################################################
 
-        self.draw()   #this is the bit you overload
+	story.append(Paragraph(
+				"Here are some examples of the remaining objects above.",
+				styleSheet['Italic']))
+	
+	story.append(Paragraph("This is a bullet point", styleSheet['Bullet'], bulletText='O'))
+	story.append(Paragraph("Another bullet point", styleSheet['Bullet'], bulletText='O'))
+	
+	story.append(Paragraph(
+				"Here is an Image.	For now, these are always centred in the frame.",
+				styleSheet['Italic']))
+
+	story.append(layout.Image('pythonpowered.gif'))
 
-        self.canv.restoreState()
-        del self.canv
-        
-    def wrap(self, availWidth, availHeight):
-        """This will be called by the enclosing frame before objects
-        are asked their size, drawn or whatever.  It returns the
-        size actually used."""
-        return (self.width, self.height)
-    '''
-    
-    story.append(layout.Preformatted(code, styleSheet['Code'], dedent=4))
-    story.append(layout.PageBreak())
-    #######################################################################
-    #     Examples Page 3
-    #######################################################################
+	story.append(Paragraph("""Here is a Table, which takes all kinds of formatting options...""",
+				styleSheet['Italic']))
+	story.append(layout.Spacer(0, 12))
+	
+	g = tables.Table(
+			(72,36,36,36,36),
+			(24, 16,16,18),
+			(('','North','South','East','West'),
+			 ('Quarter 1',100,200,300,400),
+			 ('Quarter 2',100,200,300,400),
+			 ('Total',200,400,600,800))
+			)
 
-    story.append(Paragraph(
-                "Here are some examples of the remaining objects above.",
-                styleSheet['Italic']))
-    
-    story.append(Paragraph("This is a bullet point", styleSheet['Bullet'], bulletText='O'))
-    story.append(Paragraph("Another bullet point", styleSheet['Bullet'], bulletText='O'))
-    
-    story.append(Paragraph(
-                "Here is an Image.  For now, these are always centred in the frame.",
-                styleSheet['Italic']))
-
-    story.append(layout.Image('pythonpowered.gif'))
-
-##    story.append(Paragraph("""
-##                Next comes a grid.  class BaseGrid is the ancestor of Grid classes,
-##                and doesn't do much more than this.  It, too, is centred.""",
-##                styleSheet['Italic']))
-##
-##    story.append(layout.BaseGrid((36,36,36,36),(8,8,8)))
+	style = tables.TableStyle([('ALIGN', (1,1), (-1,-1), 'RIGHT'),
+							   ('ALIGN', (0,0), (-1,0), 'CENTRE'),
+							   ('GRID', (0,0), (-1,-1), 0.25, colors.black),
+							   ('LINEBELOW', (0,0), (-1,0), 2, colors.black),
+							   ('LINEBELOW',(1,-1), (-1, -1), 2, (0.5, 0.5, 0.5)),
+							   ('TEXTCOLOR', (0,1), (0,-1), colors.black),
+							   ('BACKGROUND', (0,0), (-1,0), (0,0.7,0.7))
+							   ])
+	g.setStyle(style)
+	story.append(g)
+	story.append(FrameBreak())
+	
+	return story
 
-    story.append(Paragraph("""Here is a Table, which takes all kinds of formatting options...""",
-                styleSheet['Italic']))
-    story.append(layout.Spacer(0, 12))
-    
-    g = tables.Table(
-            (72,36,36,36,36),
-            (24, 16,16,18),
-            (('','North','South','East','West'),
-             ('Quarter 1',100,200,300,400),
-             ('Quarter 2',100,200,300,400),
-             ('Total',200,400,600,800))
-            )
+class AndyTemplate(BaseDocTemplate):
+	def __init__(self, filename, pagesize=layout.DEFAULT_PAGE_SIZE, showBoundary=0,
+				leftMargin=inch, rightMargin=inch, topMargin=inch, bottomMargin=inch,
+				allowSplitting=0):
+		frame1 = layout.BasicFrame(inch, 5.6*inch, 6*inch, 5.2*inch,id='F1')
+		frame2 = layout.BasicFrame(inch, inch, 6*inch, 4.5*inch, showBoundary=1,id='F2')
+		BaseDocTemplate.__init__(self, filename, pagesize=pagesize,
+				pageTemplates=PageTemplate('normal',[frame1,frame2],framePage),
+				showBoundary=showBoundary, leftMargin=leftMargin, rightMargin=rightMargin,
+				topMargin=topMargin, bottomMargin=bottomMargin,
+				allowSplitting=allowSplitting)
 
-##    #add some lines
-##    g.addLines(0, 0, -1, -1, 0.25)  #inner grid
-##    g.addLines(0, 1, -1, 1, 2)  #fattish top line
-##    g.addLines(0, -2, -1, -2, 2)  #fattish bottom 2 lines
-##    g.addLines(0, -1, -1, -1, 2)  #fattish bottom 2 lines
-    style = tables.TableStyle([('ALIGN', (1,1), (-1,-1), 'RIGHT'),
-                               ('ALIGN', (0,0), (-1,0), 'CENTRE'),
-                               ('GRID', (0,0), (-1,-1), 0.25, colors.black),
-                               ('LINEBELOW', (0,0), (-1,0), 2, colors.black),
-                               ('LINEBELOW',(1,-1), (-1, -1), 2, (0.5, 0.5, 0.5)),
-                               ('TEXTCOLOR', (0,1), (0,-1), colors.black),
-                               ('BACKGROUND', (0,0), (-1,0), (0,0.7,0.7))
-                               ])
-    g.setStyle(style)
-    story.append(g)
-    
-    return story
-    
+	def	fillFrame(self,flowables):
+		f = self.frame
+		while len(flowables)>0 and f is self.frame:
+			self.handle_flowable(flowables)
+
+	def build(self, flowables1, flowables2):
+		assert filter(lambda x: not isinstance(x,Flowable), flowables1)==[], "flowables1 argument error"
+		assert filter(lambda x: not isinstance(x,Flowable), flowables2)==[], "flowables2 argument error"
+		self._startBuild()
+		while (len(flowables1) > 0 or len(flowables1) > 0):
+			self.clean_hanging()
+			self.fillFrame(flowables1)
+			self.fillFrame(flowables2)
+
+		self._endBuild()
 
 def run():
-    #Rather than using a SimpleFlowDocument, this will manually construct
-    #some frames, since we need fine control for the demo.
-    #each page has a main frame for the commentary, and
-    #another for the demo/test functionality.
-    cnvs = canvas.Canvas('testlayout.pdf')
-
-    commentary = getCommentary()
-    examples = getExamples()
+	doc = AndyTemplate('testplatypus.pdf')
 
-    #while either story has data left, keep creating pages
-    #and drawing into them
-    firstPage = 1
-    while (len(examples) > 0 or len(commentary) > 0):
-        framePage(cnvs)
-        frame1 = layout.SimpleFrame(cnvs, inch, 5.6*inch, 6 * inch, 5.2 * inch)
-        frame2 = layout.SimpleFrame(cnvs, inch, inch, 6 * inch, 4.5 * inch)
-        frame2.showBoundary = 1
-        frame1.addFromList(commentary)
-        frame2.addFromList(examples)
+	commentary = getCommentary()
+	examples = getExamples()
+	doc.build(commentary,examples)
 
-        cnvs.showPage()
-        
-    cnvs.save()
-    
-    
 if __name__ == "__main__":
-    run()
+	run()