PLATYPUS - Page Layout and Typography Using Scripts
Design Goals
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.
The design of Platypus seeks to separate "high level" layout decisions from the document content as much as possible. Thus, for example, paragraphs are constructed using paragraph styles and pages are constructed using page templates with the intention that hundreds of documents with thousands of pages can be reformatted to different style specifications with the modifications of a few lines in a single shared file which contains the paragraph styles and page layout specifications.
The overall design of Platypus can be thought of has having several layers, top down, these are
DocTemplates
the outermost container for the document;
PageTemplates
specifications for layouts of pages of various kinds;
Frames
specifications of regions in pages that can contain flowing text or graphics.
Flowables
text or graphic elements that should be "flowed
into the document (i.e. things like images, paragraphs and tables, but not things
like page footers or fixed page graphics).
pdfgen.Canvas
the lowest level which ultimately receives the painting of the
document from the other layers.
The illustration above graphically illustrates the concepts of DocTemplates
,
PageTemplates
and Flowables
. It is deceptive, however, because each
of the PageTemplates
actually may specify the format for any number of pages
(not just one as might be inferred from the diagram).
DocTemplates
contain one or more PageTemplates
each of which contain one or more
Frames. Flowables
are things which can be flowed into a Frame
e.g.
a Paragraph
or a Table
.
To use platypus you create a document from a DocTemplate
class and pass
a list of Flowable
s to its build
method. The document
build
method knows how to process the list of flowables
into something reasonable.
Internally the DocTemplate
class implements page layout and formatting
using various events. Each of the events has a corresponding handler method
called handle_XXX
where XXX
is the event name. A typical event is
frameBegin
which occurs when the machinery begins to use a frame for the
first time.
A Platypus story consists of a sequence of basic elements called Flowables
and these elements drive the data driven Platypus formatting engine.
To modify the behavior of the engine
a special kind of flowable, ActionFlowables
, tell the layout engine to,
for example, skip to the next
column or change to another PageTemplate
.
Getting started
Consider the following code sequence which provides a very simple "hello world" example for Platypus.
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.rl_config import defaultPageSize
from reportlab.lib.units import inch
PAGE_HEIGHT=defaultPageSize[1]; PAGE_WIDTH=defaultPageSize[0]
styles = getSampleStyleSheet()
First we import some constructors, some paragraph styles and other conveniences from other modules.
Title = "Hello world"
pageinfo = "platypus example"
def myFirstPage(canvas, doc):
canvas.saveState()
canvas.setFont('Times-Bold',16)
canvas.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, Title)
canvas.setFont('Times-Roman',9)
canvas.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo)
canvas.restoreState()
We define the fixed features of the first page of the document with the function above.
def myLaterPages(canvas, doc):
canvas.saveState()
canvas.setFont('Times-Roman',9)
canvas.drawString(inch, 0.75 * inch, "Page %d %s" % (doc.page, pageinfo))
canvas.restoreState()
Since we want pages after the first to look different from the
first we define an alternate layout for the fixed features
of the other pages. Note that the two functions above use
the pdfgen
level canvas operations to paint the annotations for
the pages.
def go():
doc = SimpleDocTemplate("phello.pdf")
Story = [Spacer(1,2*inch)]
style = styles["Normal"]
for i in range(100):
bogustext = ("This is Paragraph number %s. " % i) *20
p = Paragraph(bogustext, style)
Story.append(p)
Story.append(Spacer(1,0.2*inch))
doc.build(Story, onFirstPage=myFirstPage, onLaterPages=myLaterPages)
Finally, we create a story and build the document.
Note that we are using a "canned" document template here which
comes pre-built with page templates. We are also using a pre-built
paragraph style. We are only using two types of flowables here
-- Spacers
and Paragraphs
. The first Spacer
ensures that the
Paragraphs skip past the title string.
To see the output of this example program run the file
tools/docco/examples.py
(from the ReportLab source
distribution)
as a "top level script". The command python examples.py
will
generate the Platypus output phello.pdf
.
Flowables
Flowables
are things which can be drawn and which have wrap
, draw
and perhaps split
methods.
Flowable
is an abstract base class for things to be drawn and an instance knows its size
and draws in its own coordinate system (this requires the base API to provide an absolute coordinate
system when the Flowable.draw
method is called). To get an instance use f=Flowable()
.
It should be noted that the Flowable
class is an abstract class and is normally
only used as a base class.
k=startKeep()
To illustrate the general way in which Flowables
are used we show how a derived class Paragraph
is used and drawn on a canvas. Paragraphs
are so important they will get a whole chapter
to themselves.
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph
from reportlab.pdfgen.canvas import Canvas
styleSheet = getSampleStyleSheet()
style = styleSheet['BodyText']
P=Paragraph('This is a very silly example',style)
canv = Canvas('doc.pdf')
aW = 460 # available width and height
aH = 800
w,h = P.wrap(aW, aH) # find required space
if w<=aW and h<=aH:
P.drawOn(canv,0,aH)
aH = aH - h # reduce the available height
canv.save()
else:
raise ValueError, "Not enough room"
endKeep(k)
Flowable User Methods
Flowable.draw()
This will be called to ask the flowable to actually render itself.
The Flowable
class does not implement draw
.
The calling code should ensure that the flowable has an attribute canv
which is the pdfgen.Canvas
which should be drawn to an that the Canvas
is in an appropriate state (as regards translations rotations, etc). Normally
this method will only be called internally by the drawOn
method. Derived classes
must implement this method.
Flowable.drawOn(canvas,x,y)
This is the method which controlling programs use to render the flowable to a particular
canvas. It handles the translation to the canvas coordinate (x,y) and ensuring that
the flowable has a canv
attribute so that the
draw
method (which is not implemented in the base class) can render in an
absolute coordinate frame.
Flowable.wrap(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.
Flowable.split(self, availWidth, availheight):
This will be called by more sophisticated frames when
wrap fails. Stupid flowables should return [] meaning that they are unable to split.
Clever flowables should split themselves and return a list of flowables. It is up to
the client code to ensure that repeated attempts to split are avoided.
If the space is sufficient the split method should return [self].
Otherwise
the flowable should rearrange itself and return a list [f0,...]
of flowables
which will be considered in order. The implemented split method should avoid
changing self
as this will allow sophisticated layout mechanisms to do multiple
passes over a list of flowables.
Guidelines for flowable positioning
Two methods, which by default return zero, provide guidance on vertical spacing of flowables:
Flowable.getSpaceAfter(self):
Flowable.getSpaceBefore(self):
These methods return how much space should follow or precede
the flowable. The space doesn't belong to the flowable itself i.e. the flowable's
draw
method shouldn't consider it when rendering. Controlling programs
will use the values returned in determining how much space is required by
a particular flowable in context.
All flowables have an hAlign
property: ('LEFT', 'RIGHT', 'CENTER' or 'CENTRE')
.
For paragraphs, which fill the full width of the frame, this has no effect. For tables,
images or other objects which are less than the width of the frame, this determines their
horizontal placement.
The chapters which follow will cover the most important specific types of flowables: Paragraphs and Tables.
Frames
Frames are active containers which are themselves contained in PageTemplates
.
Frames have a location and size and maintain a concept of remaining drawable
space. The command
Frame(x1, y1, width,height, leftPadding=6, bottomPadding=6,
rightPadding=6, topPadding=6, id=None, showBoundary=0)
creates a Frame
instance with lower left hand corner at coordinate (x1,y1)
(relative to the canvas at use time) and with dimensions width
x height
. The Padding
arguments are positive quantities used to reduce the space available for drawing.
The id
argument is an identifier for use at runtime e.g. 'LeftColumn' or 'RightColumn' etc.
If the showBoundary
argument is non-zero then the boundary of the frame will get drawn
at run time (this is useful sometimes).
Frame User Methods
Frame.addFromList(drawlist, canvas)
consumes Flowables
from the front of drawlist
until the
frame is full. If it cannot fit one object, raises
an exception.
Frame.split(flowable,canv)
'''Asks the flowable to split using up the available space and return the list of flowables. '''
Frame.drawBoundary(canvas)
"draws the frame boundary as a rectangle (primarily for debugging)."
Using Frames
Frames can be used directly with canvases and flowables to create documents.
The Frame.addFromList
method handles the wrap
& drawOn
calls for you.
You don't need all of the Platypus machinery to get something useful into
PDF.
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import Paragraph, Frame
styles = getSampleStyleSheet()
styleN = styles['Normal']
styleH = styles['Heading1']
story = []
#add some flowables
story.append(Paragraph("This is a Heading",styleH))
story.append(Paragraph("This is a paragraph in <i>Normal</i> style.",
styleN))
c = Canvas('mydoc.pdf')
f = Frame(inch, inch, 6*inch, 9*inch, showBoundary=1)
f.addFromList(story,c)
c.save()
Documents and Templates
The BaseDocTemplate
class implements the basic machinery for document
formatting. An instance of the class contains a list of one or more
PageTemplates
that can be used to describe the layout of information
on a single page. The build
method can be used to process
a list of Flowables
to produce a PDF document.
The BaseDocTemplate
class
BaseDocTemplate(self, filename,
pagesize=defaultPageSize,
pageTemplates=[],
showBoundary=0,
leftMargin=inch,
rightMargin=inch,
topMargin=inch,
bottomMargin=inch,
allowSplitting=1,
title=None,
author=None,
_pageBreakQuick=1,
encrypt=None)
creates a document template suitable for creating a basic document. It comes with quite a lot
of internal machinery, but no default page templates. The required filename
can be a string,
the name of a file to receive the created PDF document; alternatively it
can be an object which has a write
method such as a BytesIO
or file
or socket
.
The allowed arguments should be self explanatory, but showBoundary
controls whether or
not Frame
boundaries are drawn which can be useful for debugging purposes. The
allowSplitting
argument determines whether the builtin methods should try to split
individual Flowables
across Frames. The _pageBreakQuick
argument determines whether
an attempt to do a page break should try to end all the frames on the page or not, before ending
the page. The encrypt argument determines wether or not and how the document is encrypted.
By default, the document is not encrypted.
If encrypt
is a string object, it is used as the user password for the pdf.
If encrypt
is an instance of reportlab.lib.pdfencrypt.StandardEncryption
, this object is
used to encrypt the pdf. This allows more finegrained control over the encryption settings.
User BaseDocTemplate
Methods
These are of direct interest to client programmers in that they are normally expected to be used.
BaseDocTemplate.addPageTemplates(self,pageTemplates)
This method is used to add one or a list of PageTemplates
to an existing documents.
BaseDocTemplate.build(self, flowables, filename=None, canvasmaker=canvas.Canvas)
This is the main method which is of interest to the application
programmer. Assuming that the document instance is correctly set up the
build
method takes the story in the shape of the list of flowables
(the flowables
argument) and loops through the list forcing the flowables
one at a time through the formatting machinery. Effectively this causes
the BaseDocTemplate
instance to issue calls to the instance handle_XXX
methods
to process the various events.
User Virtual BaseDocTemplate
Methods
These have no semantics at all in the base class. They are intended as pure virtual hooks into the layout machinery. Creators of immediately derived classes can override these without worrying about affecting the properties of the layout engine.
BaseDocTemplate.afterInit(self)
This is called after initialisation of the base class; a derived class could overide
the method to add default PageTemplates
.
BaseDocTemplate.afterPage(self)
This is called after page processing, and immediately after the afterDrawPage method of the current page template. A derived class could use this to do things which are dependent on information in the page such as the first and last word on the page of a dictionary.
BaseDocTemplate.beforeDocument(self)
This is called before any processing is
done on the document, but after the processing machinery
is ready. It can therefore be used to do things to the instance\'s
pdfgen.canvas
and the like.
BaseDocTemplate.beforePage(self)
This is called at the beginning of page processing, and immediately before the beforeDrawPage method of the current page template. It could be used to reset page specific information holders.
BaseDocTemplate.filterFlowables(self,flowables)
This is called to filter flowables at the start of the main handle_flowable method. Upon return if flowables[0] has been set to None it is discarded and the main method returns immediately.
BaseDocTemplate.afterFlowable(self, flowable)
Called after a flowable has been rendered. An interested class could use this hook to gather information about what information is present on a particular page or frame.
BaseDocTemplate
Event handler Methods
These methods constitute the greater part of the layout engine. Programmers shouldn't
have to call or override these methods directly unless they are trying to modify the layout engine.
Of course, the experienced programmer who wants to intervene at a particular event, XXX
,
which does not correspond to one of the virtual methods can always override and
call the base method from the drived class version. We make this easy by providing
a base class synonym for each of the handler methods with the same name prefixed by an underscore '_'.
def handle_pageBegin(self):
doStuff()
BaseDocTemplate.handle_pageBegin(self)
doMoreStuff()
#using the synonym
def handle_pageEnd(self):
doStuff()
self._handle_pageEnd()
doMoreStuff()
Here we list the methods only as an indication of the events that are being handled.
Interested programmers can take a look at the source.
handle_currentFrame(self,fx)
handle_documentBegin(self)
handle_flowable(self,flowables)
handle_frameBegin(self,*args)
handle_frameEnd(self)
handle_nextFrame(self,fx)
handle_nextPageTemplate(self,pt)
handle_pageBegin(self)
handle_pageBreak(self)
handle_pageEnd(self)
Using document templates can be very easy; SimpleDoctemplate
is a class derived from
BaseDocTemplate
which provides its own PageTemplate
and Frame
setup.
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import letter
from reportlab.platypus import Paragraph, SimpleDocTemplate
styles = getSampleStyleSheet()
styleN = styles['Normal']
styleH = styles['Heading1']
story = []
#add some flowables
story.append(Paragraph("This is a Heading",styleH))
story.append(Paragraph("This is a paragraph in <i>Normal</i> style.",
styleN))
doc = SimpleDocTemplate('mydoc.pdf',pagesize = letter)
doc.build(story)
PageTemplates
The PageTemplate
class is a container class with fairly minimal semantics. Each instance
contains a list of Frames and has methods which should be called at the start and end
of each page.
PageTemplate(id=None,frames=[],onPage=_doNothing,onPageEnd=_doNothing)
is used to initialize an instance, the Frames argument should be a list of Frames
whilst the optional onPage
and onPageEnd
arguments are callables which should have signature
def XXX(canvas,document)
where canvas
and document
are the canvas and document being drawn. These routines are intended to be used to paint non-flowing (i.e. standard)
parts of pages. These attribute functions are exactly parallel to the pure virtual methods
PageTemplate.beforPage
and PageTemplate.afterPage
which have signature
beforPage(self,canvas,document)
. The methods allow class derivation to be used to define
standard behaviour, whilst the attributes allow instance changes. The id
argument is used at
run time to perform PageTemplate
switching so id='FirstPage'
or id='TwoColumns'
are typical.