Reportlab PDF Library User Guide: Reportlab Version 3.4.14 Document Generated On 2017/05/27 20:20:42
Reportlab PDF Library User Guide: Reportlab Version 3.4.14 Document Generated On 2017/05/27 20:20:42
Reportlab PDF Library User Guide: Reportlab Version 3.4.14 Document Generated On 2017/05/27 20:20:42
User Guide
ReportLab Version 3.4.14
Document generated on 2017/05/27 20:20:42
Thornton House
Thornton Road
Wimbledon
London SW19 4NG, UK
User Guide Table of contents
Table of contents
Table of contents 2
Chapter 1 Introduction 6
1.1 About this document . . . . . . . . . . . . . . . . . . . . . . . . . . . .6
1.2 What is the ReportLab PDF Library? . . . . . . . . . . . . . . . . . . .6
1.3 ReportLab's commercial software . . . . . . . . . . . . . . . . . . . . .7
1.4 What is Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .7
1.5 Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . .7
1.6 Installation and Setup . . . . . . . . . . . . . . . . . . . . . . . . . . .8
1.7 Getting Involved . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8
1.8 Site Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8
1.9 Learning More About Python . . . . . . . . . . . . . . . . . . . . . . .9
1.10 Goals for the 3.x release series . . . . . . . . . . . . . . . . . . . . .9
Page 2
User Guide Table of contents
Chapter 6 Paragraphs 71
6.1 Using Paragraph Styles . . . . . . . . . . . . . . . . . . . . . . . . . 72
6.2 Paragraph XML Markup Tags . . . . . . . . . . . . . . . . . . . . . . 76
6.3 Intra-paragraph markup . . . . . . . . . . . . . . . . . . . . . . . . . 77
6.4 Bullets and Paragraph Numbering . . . . . . . . . . . . . . . . . . . . 80
Page 3
User Guide Table of contents
Chapter 11 Graphics 99
11.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
11.2 General Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
11.3 Charts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
11.4 Labels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
11.5 Axes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
11.6 Bar Charts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
11.7 Line Charts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
11.8 Line Plots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
11.9 Pie Charts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
11.10 Legends . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
11.11 Shapes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
11.12 Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Page 4
User Guide Table of contents
Page 5
User Guide Chapter 1 Introduction
Chapter 1 Introduction
Page 6
User Guide Chapter 1 Introduction
1.5 Acknowledgements
Many people have contributed to ReportLab. We would like to thank in particular (in alphabetical order):
Albertas Agejevas, Alex Buck, Andre Reitz, Andrew Cutler, Andrew Mercer, Ben Echols, Benjamin Dumke,
Benn B, Chad Miller, Chris Buergi, Chris Lee, Christian Jacobs, Dinu Gherman, Edward Greve, Eric
Johnson, Felix Labrecque, Fubu @ bitbucket, Gary Poster, Germn M. Bravo, Guillaume Francois, Hans
Brand, Henning Vonbargen, Hosam Aly, Ian Stevens, James Martin-Collar, Jeff Bauer, Jerome Alet, Jerry
Casiano, Jorge Godoy, Keven D Smith, Kyle MacFarlane, Magnus Lie Hetland, Marcel Tromp, Marius
Gedminas, Mark de Wit, Matthew Duggan, Matthias Kirst, Matthias Klose, Max M, Michael Egorov,
Michael Spector, Mike Folwell, Mirko Dziadzka, Moshe Wagner, Nate Silva, Paul McNett, Peter Johnson,
PJACock, Publio da Costa Melo, Randolph Bentson, Robert Alsina, Robert Hlzl, Robert Kern, Ron Peleg,
Ruby Yocum, Simon King, Stephan Richter, Steve Halasz, Stoneleaf @ bitbucket, T Blatter, Tim Roberts,
Tomasz Swiderski, Ty Sarna, Volker Haas, Yoann Roman, and many more.
Special thanks go to Just van Rossum for his valuable assistance with font technicalities.
Moshe Wagner and Hosam Aly deserve a huge thanks for contributing to the RTL patch, which is not yet on
the trunk.
Page 7
User Guide Chapter 1 Introduction
Marius Gedminas deserves a big hand for contributing the work on TrueType fonts and we are glad to
include these in the toolkit. Finally we thank Michal Kosmulski for the DarkGarden font for and Bitstream
Inc. for the Vera fonts.
Page 8
User Guide Chapter 1 Introduction
Page 9
User Guide Chapter 2 Graphics and Text with pdfgen
The above code creates a canvas object which will generate a PDF file named hello.pdf in the current
working directory. It then calls the hello function passing the canvas as an argument. Finally the
showPage method saves the current page of the canvas and the save method stores the file and closes the
canvas.
The showPage method causes the canvas to stop drawing on the current page and any further operations
will draw on a subsequent page (if there are any further operations -- if not no new page is created). The
save method must be called after the construction of the document is complete -- it generates the PDF
document, which is the whole purpose of the canvas object.
def __init__(self,filename,
pagesize=(595.27,841.89),
bottomup = 1,
pageCompression=0,
encoding=rl_config.defaultEncoding,
verbosity=0
encrypt=None):
The filename argument controls the name of the final PDF file. You may also pass in any open file object
(such as sys.stdout, the python process standard output) and the PDF document will be written to that.
Since PDF is a binary format, you should take care when writing other stuff before or after it; you can't
deliver PDF documents inline in the middle of an HTML page!
The pagesize argument is a tuple of two numbers in points (1/72 of an inch). The canvas defaults to A4 (an
international standard page size which differs from the American standard page size of letter), but it is
better to explicitly specify it. Most common page sizes are found in the library module
reportlab.lib.pagesizes, so you can use expressions like
NOTE
Page 10
User Guide Chapter 2 Graphics and Text with pdfgen
If you have problems printing your document make sure you are using the right page size (usually either A4
or letter). Some printers do not work well with pages that are too large or too small.
Very often, you will want to calculate things based on the page size. In the example above we extracted the
width and height. Later in the program we may use the width variable to define a right margin as width -
inch rather than using a constant. By using variables the margin will still make sense even if the page size
changes.
The bottomup argument switches coordinate systems. Some graphics systems (like PDF and PostScript)
place (0,0) at the bottom left of the page others (like many graphical user interfaces [GUI's]) place the origin
at the top left. The bottomup argument is deprecated and may be dropped in future
Need to see if it really works for all tasks, and if not then get rid of it
The pageCompression option determines whether the stream of PDF operations for each page is
compressed. By default page streams are not compressed, because the compression slows the file generation
process. If output size is important set pageCompression=1, but remember that, compressed documents
will be smaller, but slower to generate. Note that images are always compressed, and this option will only
save space if you have a very large amount of text and vector graphics on each page.
The encoding argument is largely obsolete in version 2.0 and can probably be omitted by 99% of users. Its
default value is fine unless you very specifically need to use one of the 25 or so characters which are present
in MacRoman and not in Winansi. A useful reference to these is here:
http://www.alanwood.net/demos/charsetdiffs.html. The parameter determines which font encoding is used for
the standard Type 1 fonts; this should correspond to the encoding on your system. Note that this is the
encoding used internally by the font; text you pass to the ReportLab toolkit for rendering should always either
be a Python unicode string object or a UTF-8 encoded byte string (see the next chapter)! The font encoding
has two values at present: 'WinAnsiEncoding' or 'MacRomanEncoding'. The variable
rl_config.defaultEncoding above points to the former, which is standard on Windows, Mac OS X
and many Unices (including Linux). If you are Mac user and don't have OS X, you may want to make a
global change: modify the line at the top of reportlab/pdfbase/pdfdoc.py to switch it over. Otherwise, you can
probably just ignore this argument completely and never pass it. For all TTF and the commonly-used CID
fonts, the encoding you pass in here is ignored, since the reportlab library itself knows the right encodings in
those cases.
The demo script reportlab/demos/stdfonts.py will print out two test documents showing all code
points in all fonts, so you can look up characters. Special characters can be inserted into string commands
with the usual Python escape sequences; for example \101 = 'A'.
The verbosity argument determines how much log information is printed. By default, it is zero to assist
applications which want to capture PDF from standard output. With a value of 1, you will get a confirmation
message each time a document is generated. Higher numbers may give more output in future.
The encrypt argument determines if 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. Encryption is covered in more detail in
Chapter 4.
to do - all the info functions and other non-drawing stuff
Cover all constructor arguments, and setAuthor etc.
def hello(c):
from reportlab.lib.units import inch
# move the origin up and to the left
c.translate(inch,inch)
# define a large font
c.setFont("Helvetica", 14)
# choose some colors
Page 11
User Guide Chapter 2 Graphics and Text with pdfgen
c.setStrokeColorRGB(0.2,0.5,0.3)
c.setFillColorRGB(1,0,1)
# draw some lines
c.line(0,0,0,1.7*inch)
c.line(0,0,1*inch,0)
# draw a rectangle
c.rect(0.2*inch,0.2*inch,1*inch,1.5*inch, fill=1)
# make text go straight up
c.rotate(90)
# change color
c.setFillColorRGB(0,0,0.77)
# say hello (note after rotate the y coord needs to be negative!)
c.drawString(0.3*inch, -inch, "Hello World")
Examining this code notice that there are essentially two types of operations performed using a canvas. The
first type draws something on the page such as a text string or a rectangle or a line. The second type changes
the state of the canvas such as changing the current fill or stroke color or changing the current font type and
size.
If we imagine the program as a painter working on the canvas the "draw" operations apply paint to the canvas
using the current set of tools (colors, line styles, fonts, etcetera) and the "state change" operations change one
of the current tools (changing the fill color from whatever it was to blue, or changing the current font to
Times-Roman in 15 points, for example).
The document generated by the "hello world" program listed above would contain the following graphics.
Hello World
Page 12
User Guide Chapter 2 Graphics and Text with pdfgen
Line methods
canvas.line(x1,y1,x2,y2)
canvas.lines(linelist)
Shape methods
canvas.grid(xlist, ylist)
canvas.arc(x1,y1,x2,y2)
canvas.drawString(x, y, text):
canvas.drawRightString(x, y, text)
canvas.drawCentredString(x, y, text)
The draw string methods draw single lines of text on the canvas.
textobject = canvas.beginText(x, y)
canvas.drawText(textobject)
Text objects are used to format text in ways that are not supported directly by the canvas interface. A
program creates a text object from the canvas using beginText and then formats text by invoking
textobject methods. Finally the textobject is drawn onto the canvas using drawText.
path = canvas.beginPath()
Path objects are similar to text objects: they provide dedicated control for performing complex graphical
drawing not directly provided by the canvas interface. A program creates a path object using beginPath
populates the path with graphics using the methods of the path object and then draws the path on the canvas
Page 13
User Guide Chapter 2 Graphics and Text with pdfgen
using drawPath.
It is also possible to use a path as a "clipping region" using the clipPath method -- for example a circular
path can be used to clip away the outer parts of a rectangular image leaving only a circular part of the image
visible on the page.
If fill=1 is specified then the fillMode argument may be used to set either 0=even-odd or
1=non-zero filling mode. which will alter the way that complex paths are filled. If the default None values
is used then the canvas _fillMode attribute value is used (normally 0 ie even-odd).
Image methods
NOTE You need the Python Imaging Library (PIL) to use images with the ReportLab package. Examples of the
techniques below can be found by running the script test_pdfgen_general.py in our tests
subdirectory and looking at page 7 of the output.
There are two similar-sounding ways to draw images. The preferred one is the drawImage method. This
implements a caching system so you can define an image once and draw it many times; it will only be stored
once in the PDF file. drawImage also exposes one advanced parameter, a transparency mask, and will
expose more in future. The older technique, drawInlineImage, stores bitmaps within the page stream and
is thus very inefficient if you use the same image more than once in a document; but can result in PDFs which
render faster if the images are very small and not repeated. We'll discuss the oldest one first:
The drawInlineImage method places an image on the canvas. The image parameter may be either a PIL
Image object or an image filename. Many common file formats are accepted including GIF and JPEG. It
returns the size of the actual image in pixels as a (width, height) tuple.
The arguments and return value work as for drawInlineImage. However, we use a caching system; a
given image will only be stored the first time it is used, and just referenced on subsequent use. If you supply a
filename, it assumes that the same filename means the same image. If you supply a PIL image, it tests if the
content has actually changed before re-embedding.
The mask parameter lets you create transparent images. It takes 6 numbers and defines the range of RGB
values which will be masked out or treated as transparent. For example with [0,2,40,42,136,139], it will mask
out any pixels with a Red value from 0 or 1, Green from 40 or 41 and Blue of 136, 137 or 138 (on a scale of
0-255). It's currently your job to know which color is the 'transparent' or background one.
PDF allows for many image features and we will expose more of the over time, probably with extra keyword
arguments to drawImage.
Ending a page
canvas.showPage()
The showPage method finishes the current page. All additional drawing will be done on another page.
NOTE Warning! All state changes (font changes, color settings, geometry transforms, etcetera) are FORGOTTEN
when you advance to a new page in pdfgen. Any state settings you wish to preserve must be set up again
before the program proceeds with drawing!
Page 14
User Guide Chapter 2 Graphics and Text with pdfgen
Changing Colors
canvas.setFillColorCMYK(c, m, y, k)
canvas.setStrikeColorCMYK(c, m, y, k)
canvas.setFillColorRGB(r, g, b)
canvas.setStrokeColorRGB(r, g, b)
canvas.setFillColor(acolor)
canvas.setStrokeColor(acolor)
canvas.setFillGray(gray)
canvas.setStrokeGray(gray)
PDF supports three different color models: gray level, additive (red/green/blue or RGB), and subtractive with
darkness parameter (cyan/magenta/yellow/darkness or CMYK). The ReportLab packages also provide named
colors such as lawngreen. There are two basic color parameters in the graphics state: the Fill color for
the interior of graphic figures and the Stroke color for the boundary of graphic figures. The above methods
support setting the fill or stroke color using any of the four color specifications.
Changing Fonts
The setFont method changes the current text font to a given type and size. The leading parameter
specifies the distance down to move when advancing from one text line to the next.
canvas.setLineWidth(width)
canvas.setLineCap(mode)
canvas.setLineJoin(mode)
canvas.setMiterLimit(limit)
Lines drawn in PDF can be presented in a number of graphical styles. Lines can have different widths, they
can end in differing cap styles, they can meet in different join styles, and they can be continuous or they can
be dotted or dashed. The above methods adjust these various parameters.
Changing Geometry
canvas.setPageSize(pair)
canvas.transform(a,b,c,d,e,f):
canvas.translate(dx, dy)
canvas.scale(x, y)
canvas.rotate(theta)
Page 15
User Guide Chapter 2 Graphics and Text with pdfgen
canvas.skew(alpha, beta)
All PDF drawings fit into a specified page size. Elements drawn outside of the specified page size are not
visible. Furthermore all drawn elements are passed through an affine transformation which may adjust their
location and/or distort their appearence. The setPageSize method adjusts the current page size. The
transform, translate, scale, rotate, and skew methods add additional transformations to the
current transformation. It is important to remember that these transformations are incremental -- a new
transform modifies the current transform (but does not replace it).
State control
canvas.saveState()
canvas.restoreState()
Very often it is important to save the current font, graphics transform, line styles and other graphics state in
order to restore them later. The saveState method marks the current graphics state for later restoration by
a matching restoreState. Note that the save and restore method invokation must match -- a restore call
restores the state to the most recently saved state which hasn't been restored yet. You cannot save the state on
one page and restore it on the next, however -- no state is preserved between pages.
canvas.setAuthor()
canvas.addOutlineEntry(title, key, level=0, closed=None)
canvas.setTitle(title)
canvas.setSubject(subj)
canvas.pageHasData()
canvas.showOutline()
canvas.bookmarkPage(name)
canvas.bookmarkHorizontalAbsolute(name, yhorizontal)
canvas.doForm()
canvas.beginForm(name, lowerx=0, lowery=0, upperx=None, uppery=None)
canvas.endForm()
canvas.linkAbsolute(contents, destinationname, Rect=None, addtopage=1, name=None, **kw)
canvas.linkRect(contents, destinationname, Rect=None, addtopage=1, relative=1, name=None, **kw)
canvas.getPageNumber()
canvas.addLiteral()
canvas.getAvailableFonts()
canvas.stringWidth(self, text, fontName, fontSize, encoding=None)
canvas.setPageCompression(onoff=1)
canvas.setPageTransition(self, effectname=None, duration=1,
direction=0,dimension='H',motion='I')
def coords(canvas):
from reportlab.lib.units import inch
from reportlab.lib.colors import pink, black, red, blue, green
c = canvas
c.setStrokeColor(pink)
c.grid([inch, 2*inch, 3*inch, 4*inch], [0.5*inch, inch, 1.5*inch, 2*inch, 2.5*inch])
c.setStrokeColor(black)
c.setFont("Times-Roman", 20)
c.drawString(0,0, "(0,0) the Origin")
c.drawString(2.5*inch, inch, "(2.5,1) in inches")
c.drawString(4*inch, 2.5*inch, "(4, 2.5)")
c.setFillColor(red)
Page 16
User Guide Chapter 2 Graphics and Text with pdfgen
c.rect(0,2*inch,0.2*inch,0.3*inch, fill=1)
c.setFillColor(green)
c.circle(4.5*inch, 0.4*inch, 0.2*inch, fill=1)
In the default user space the "origin" (0,0) point is at the lower left corner. Executing the coords function
in the default user space (for the "demo minipage") we obtain the following.
(4, 2.5)
(2.5,1) in inches
def translate(canvas):
from reportlab.lib.units import cm
canvas.translate(2.3*cm, 0.3*cm)
coords(canvas)
Page 17
User Guide Chapter 2 Graphics and Text with pdfgen
(4, 2.5)
(2.5,1) in inches
NOTE Note: As illustrated in the example it is perfectly possible to draw objects or parts of objects "off the page". In
particular a common confusing bug is a translation operation that translates the entire drawing off the visible
area of the page. If a program produces a blank page it is possible that all the drawn objects are off the page.
def scale(canvas):
canvas.scale(0.75, 0.5)
coords(canvas)
This produces a "short and fat" reduced version of the previously displayed operations.
Page 18
User Guide Chapter 2 Graphics and Text with pdfgen
(4, 2.5)
(2.5,1) in inches
NOTE Note: scaling may also move objects or parts of objects off the page, or may cause objects to "shrink to
nothing."
Scaling and translation can be combined, but the order of the operations are important.
def scaletranslate(canvas):
from reportlab.lib.units import inch
canvas.setFont("Courier-BoldOblique", 12)
# save the state
canvas.saveState()
# scale then translate
canvas.scale(0.3, 0.5)
canvas.translate(2.4*inch, 1.5*inch)
canvas.drawString(0, 2.7*inch, "Scale then translate")
coords(canvas)
# forget the scale and translate...
canvas.restoreState()
# translate then scale
canvas.translate(2.4*inch, 1.5*inch)
canvas.scale(0.3, 0.5)
canvas.drawString(0, 2.7*inch, "Translate then scale")
coords(canvas)
This example function first saves the current canvas state and then does a scale followed by a
translate. Afterward the function restores the state (effectively removing the effects of the scaling and
translation) and then does the same operations in a different order. Observe the effect below.
Page 19
User Guide Chapter 2 Graphics and Text with pdfgen
NOTE Note: scaling shrinks or grows everything including line widths so using the canvas.scale method to render a
microscopic drawing in scaled microscopic units may produce a blob (because all line widths will get
expanded a huge amount). Also rendering an aircraft wing in meters scaled to centimeters may cause the lines
to shrink to the point where they disappear. For engineering or scientific purposes such as these scale and
translate the units externally before rendering them using the canvas.
Mirror image
It is interesting although perhaps not terribly useful to note that scale factors can be negative. For example
the following function
def mirror(canvas):
from reportlab.lib.units import inch
canvas.translate(5.5*inch, 0)
canvas.scale(-1.0, 1.0)
coords(canvas)
Page 20
User Guide Chapter 2 Graphics and Text with pdfgen
)5.2 ,4(
sehcni ni )1,5.2(
2.8 Colors
There are generally two types of colors used in PDF depending on the media where the PDF will be used.
The most commonly known screen colors model RGB can be used in PDF, however in professional printing
another color model CMYK is mainly used which gives more control over how inks are applied to paper.
More on these color models below.
RGB Colors
The RGB or additive color representation follows the way a computer screen adds different levels of the red,
green, and blue light to make any color in between, where white is formed by turning all three lights on full
(1,1,1).
There are three ways to specify RGB colors in pdfgen: by name (using the color module, by
red/green/blue (additive, RGB) value, or by gray level. The colors function below exercises each of the four
methods.
def colorsRGB(canvas):
from reportlab.lib import colors
from reportlab.lib.units import inch
black = colors.black
y = x = 0; dy=inch*3/4.0; dx=inch*5.5/5; w=h=dy/2; rdx=(dx-w)/2
rdy=h/5.0; texty=h+2*rdy
canvas.setFont("Helvetica",10)
for [namedcolor, name] in (
[colors.lavenderblush, "lavenderblush"],
[colors.lawngreen, "lawngreen"],
[colors.lemonchiffon, "lemonchiffon"],
[colors.lightblue, "lightblue"],
[colors.lightcoral, "lightcoral"]):
canvas.setFillColor(namedcolor)
canvas.rect(x+rdx, y+rdy, w, h, fill=1)
canvas.setFillColor(black)
canvas.drawCentredString(x+dx/2, y+texty, name)
x = x+dx
y = y + dy; x = 0
for rgb in [(1,0,0), (0,1,0), (0,0,1), (0.5,0.3,0.1), (0.4,0.5,0.3)]:
r,g,b = rgb
canvas.setFillColorRGB(r,g,b)
canvas.rect(x+rdx, y+rdy, w, h, fill=1)
canvas.setFillColor(black)
Page 21
User Guide Chapter 2 Graphics and Text with pdfgen
gray: 0.0 gray: 0.25 gray: 0.5 gray: 0.75 gray: 1.0
def alpha(canvas):
from reportlab.graphics.shapes import Rect
from reportlab.lib.colors import Color, black, blue, red
red50transparent = Color( 100, 0, 0, alpha=0.5)
c = canvas
c.setFillColor(black)
c.setFont('Helvetica', 10)
c.drawString(25,180, 'solid')
c.setFillColor(blue)
c.rect(25,25,100,100, fill=True, stroke=False)
c.setFillColor(red)
c.rect(100,75,100,100, fill=True, stroke=False)
c.setFillColor(black)
c.drawString(225,180, 'transparent')
Page 22
User Guide Chapter 2 Graphics and Text with pdfgen
c.setFillColor(blue)
c.rect(225,25,100,100, fill=True, stroke=False)
c.setFillColor(red50transparent)
c.rect(300,75,100,100, fill=True, stroke=False)
solid transparent
CMYK Colors
The CMYK or subtractive method follows the way a printer mixes three pigments (cyan, magenta, and yellow)
to form colors. Because mixing chemicals is more difficult than combining light there is a fourth parameter
for darkness. For example a chemical combination of the CMY pigments generally never makes a perfect
black -- instead producing a muddy color -- so, to get black printers don not use the CMY pigments but use a
direct black ink. Because CMYK maps more directly to the way printer hardware works it may be the case that
colors specified in CMYK will provide better fidelity and better control when printed.
There are two ways of representing CMYK Color: each color can be represented either by a real value
between 0 and 1, or integer value between 0 and 100. Depending on your preference you can either use
CMYKColor (for real values) or PCMYKColor ( for integer values). 0 means 'no ink', so printing on white
papers gives you white. 1 (or 100 if you use PCMYKColor) means 'the maximum amount of ink'. e.g.
CMYKColor(0,0,0,1) is black, CMYKColor(0,0,0,0) means 'no ink', and CMYKColor(0.5,0,0,0) means 50
percent cyan color.
def colorsCMYK(canvas):
from reportlab.lib.colors import CMYKColor, PCMYKColor
from reportlab.lib.units import inch
# creates a black CMYK ; CMYKColor use real values
black = CMYKColor(0,0,0,1)
# creates a cyan CMYK ; PCMYKColor use integer values
cyan = PCMYKColor(100,0,0,0)
y = x = 0; dy=inch*3/4.0; dx=inch*5.5/5; w=h=dy/2; rdx=(dx-w)/2
rdy=h/5.0; texty=h+2*rdy
canvas.setFont("Helvetica",10)
y = y + dy; x = 0
for cmyk in [(1,0,0,0), (0,1,0,0), (0,0,1,0), (0,0,0,1), (0,0,0,0)]:
c,m,y1,k = cmyk
canvas.setFillColorCMYK(c,m,y1,k)
canvas.rect(x+rdx, y+rdy, w, h, fill=1)
canvas.setFillColor(black)
canvas.drawCentredString(x+dx/2, y+texty, "c%s m%s y%s k%s"%cmyk)
x = x+dx
Page 23
User Guide Chapter 2 Graphics and Text with pdfgen
c1 m0 y0 k0 c0 m1 y0 k0 c0 m0 y1 k0 c0 m0 y0 k1 c0 m0 y0 k0
Page 24
User Guide Chapter 2 Graphics and Text with pdfgen
overprint knockout
def spumoni(canvas):
from reportlab.lib.units import inch
from reportlab.lib.colors import pink, green, brown, white
x = 0; dx = 0.4*inch
for i in range(4):
for color in (pink, green, brown):
canvas.setFillColor(color)
canvas.rect(x,0,dx,3*inch,stroke=0,fill=1)
x = x+dx
canvas.setFillColor(white)
canvas.setStrokeColor(white)
canvas.setFont("Helvetica-Bold", 85)
canvas.drawCentredString(2.75*inch, 1.3*inch, "SPUMONI")
SPUMONI
Figure 2-11: Painting over colors
Page 25
User Guide Chapter 2 Graphics and Text with pdfgen
The last letters of the word are not visible because the default canvas background is white and painting
white letters over a white background leaves no visible effect.
This method of building up complex paintings in layers can be done in very many layers in pdfgen -- there
are fewer physical limitations than there are when dealing with physical paints.
def spumoni2(canvas):
from reportlab.lib.units import inch
from reportlab.lib.colors import pink, green, brown, white, black
# draw the previous drawing
spumoni(canvas)
# now put an ice cream cone on top of it:
# first draw a triangle (ice cream cone)
p = canvas.beginPath()
xcenter = 2.75*inch
radius = 0.45*inch
p.moveTo(xcenter-radius, 1.5*inch)
p.lineTo(xcenter+radius, 1.5*inch)
p.lineTo(xcenter, 0)
canvas.setFillColor(brown)
canvas.setStrokeColor(black)
canvas.drawPath(p, fill=1)
# draw some circles (scoops)
y = 1.5*inch
for color in (pink, green, brown):
canvas.setFillColor(color)
canvas.circle(xcenter, y, radius, fill=1)
y = y+radius
The spumoni2 function layers an ice cream cone over the spumoni drawing. Note that different parts of
the cone and scoops layer over eachother as well.
SPUMONI
Figure 2-12: building up a drawing in layers
def textsize(canvas):
from reportlab.lib.units import inch
from reportlab.lib.colors import magenta, red
canvas.setFont("Times-Roman", 20)
canvas.setFillColor(red)
canvas.drawCentredString(2.75*inch, 2.5*inch, "Font size examples")
canvas.setFillColor(magenta)
Page 26
User Guide Chapter 2 Graphics and Text with pdfgen
size = 7
y = 2.3*inch
x = 1.3*inch
for line in lyrics:
canvas.setFont("Helvetica", size)
canvas.drawRightString(x,y,"%s points: " % size)
canvas.drawString(x,y, line)
y = y-size*1.2
size = size+1.5
def fonts(canvas):
from reportlab.lib.units import inch
text = "Now is the time for all good men to..."
x = 1.8*inch
y = 2.7*inch
for font in canvas.getAvailableFonts():
canvas.setFont(font, 10)
canvas.drawString(x,y,text)
canvas.setFont("Helvetica", 10)
canvas.drawRightString(x-10,y, font+":")
y = y-13
The fonts function lists the fonts that are always available. These don't need to be stored in a PDF
document, since they are guaranteed to be present in Acrobat Reader.
Page 27
User Guide Chapter 2 Graphics and Text with pdfgen
The Symbol and ZapfDingbats fonts cannot display properly because the required glyphs are not present in
those fonts.
For information on how to use arbitrary fonts, see the next chapter.
textobject.setTextOrigin(x,y)
textobject.setTextTransform(a,b,c,d,e,f)
(x,y) = textobject.getCursor()
x = textobject.getX(); y = textobject.getY()
textobject.textOut(text)
textobject.textLine(text='')
textobject.textLines(stuff, trim=1)
The text object methods shown above relate to basic text geometry.
A text object maintains a text cursor which moves about the page when text is drawn. For example the
setTextOrigin places the cursor in a known position and the textLine and textLines methods
move the text cursor down past the lines that have been missing.
def cursormoves1(canvas):
from reportlab.lib.units import inch
textobject = canvas.beginText()
textobject.setTextOrigin(inch, 2.5*inch)
textobject.setFont("Helvetica-Oblique", 14)
for line in lyrics:
Page 28
User Guide Chapter 2 Graphics and Text with pdfgen
textobject.textLine(line)
textobject.setFillGray(0.4)
textobject.textLines('''
With many apologies to the Beach Boys
and anyone else who finds this objectionable
''')
canvas.drawText(textobject)
The cursormoves function relies on the automatic movement of the text cursor for placing text after the
origin has been set.
It is also possible to control the movement of the cursor more explicitly by using the moveCursor method
(which moves the cursor as an offset from the start of the current line NOT the current cursor, and which also
has positive y offsets move down (in contrast to the normal geometry where positive y usually moves up.
def cursormoves2(canvas):
from reportlab.lib.units import inch
textobject = canvas.beginText()
textobject.setTextOrigin(2, 2.5*inch)
textobject.setFont("Helvetica-Oblique", 14)
for line in lyrics:
textobject.textOut(line)
textobject.moveCursor(14,14) # POSITIVE Y moves down!!!
textobject.setFillColorRGB(0.4,0,1)
textobject.textLines('''
With many apologies to the Beach Boys
and anyone else who finds this objectionable
''')
canvas.drawText(textobject)
Here the textOut does not move the down a line in contrast to the textLine function which does move
down.
Page 29
User Guide Chapter 2 Graphics and Text with pdfgen
Character Spacing
textobject.setCharSpace(charSpace)
The setCharSpace method adjusts one of the parameters of text -- the inter-character spacing.
def charspace(canvas):
from reportlab.lib.units import inch
textobject = canvas.beginText()
textobject.setTextOrigin(3, 2.5*inch)
textobject.setFont("Helvetica-Oblique", 10)
charspace = 0
for line in lyrics:
textobject.setCharSpace(charspace)
textobject.textLine("%s: %s" %(charspace,line))
charspace = charspace+0.5
textobject.setFillGray(0.4)
textobject.textLines('''
With many apologies to the Beach Boys
and anyone else who finds this objectionable
''')
canvas.drawText(textobject)
The charspace function exercises various spacing settings. It produces the following page.
Page 30
User Guide Chapter 2 Graphics and Text with pdfgen
Word Spacing
textobject.setWordSpace(wordSpace)
def wordspace(canvas):
from reportlab.lib.units import inch
textobject = canvas.beginText()
textobject.setTextOrigin(3, 2.5*inch)
textobject.setFont("Helvetica-Oblique", 12)
wordspace = 0
for line in lyrics:
textobject.setWordSpace(wordspace)
textobject.textLine("%s: %s" %(wordspace,line))
wordspace = wordspace+2.5
textobject.setFillColorCMYK(0.4,0,0.4,0.2)
textobject.textLines('''
With many apologies to the Beach Boys
and anyone else who finds this objectionable
''')
canvas.drawText(textobject)
The wordspace function shows what various word space settings look like below.
Page 31
User Guide Chapter 2 Graphics and Text with pdfgen
Horizontal Scaling
textobject.setHorizScale(horizScale)
def horizontalscale(canvas):
from reportlab.lib.units import inch
textobject = canvas.beginText()
textobject.setTextOrigin(3, 2.5*inch)
textobject.setFont("Helvetica-Oblique", 12)
horizontalscale = 80 # 100 is default
for line in lyrics:
textobject.setHorizScale(horizontalscale)
textobject.textLine("%s: %s" %(horizontalscale,line))
horizontalscale = horizontalscale+10
textobject.setFillColorCMYK(0.0,0.4,0.4,0.2)
textobject.textLines('''
With many apologies to the Beach Boys
and anyone else who finds this objectionable
''')
canvas.drawText(textobject)
The horizontal scaling parameter horizScale is given in percentages (with 100 as the default), so the 80
setting shown below looks skinny.
Page 32
User Guide Chapter 2 Graphics and Text with pdfgen
textobject.setLeading(leading)
The vertical offset between the point at which one line starts and where the next starts is called the leading
offset. The setLeading method adjusts the leading offset.
def leading(canvas):
from reportlab.lib.units import inch
textobject = canvas.beginText()
textobject.setTextOrigin(3, 2.5*inch)
textobject.setFont("Helvetica-Oblique", 14)
leading = 8
for line in lyrics:
textobject.setLeading(leading)
textobject.textLine("%s: %s" %(leading,line))
leading = leading+2.5
textobject.setFillColorCMYK(0.8,0,0,0.3)
textobject.textLines('''
With many apologies to the Beach Boys
and anyone else who finds this objectionable
''')
canvas.drawText(textobject)
As shown below if the leading offset is set too small characters of one line my write over the bottom parts of
characters in the previous line.
Page 33
User Guide Chapter 2 Graphics and Text with pdfgen
textobject.setTextRenderMode(mode)
The setTextRenderMode method allows text to be used as a forground for clipping background
drawings, for example.
textobject.setRise(rise)
The setRise method raises or lowers text on the line (for creating superscripts or subscripts, for example).
textobject.setFillColor(aColor);
textobject.setStrokeColor(self, aColor)
# and similar
These color change operations change the color of the text and are otherwise similar to the color methods for
the canvas object.
Page 34
User Guide Chapter 2 Graphics and Text with pdfgen
The star function has been designed to be useful in illustrating various line style parameters supported by
pdfgen.
Title Here
Comment here.
def joins(canvas):
from reportlab.lib.units import inch
# make lines big
canvas.setLineWidth(5)
star(canvas, "Default: mitered join", "0: pointed", xcenter = 1*inch)
canvas.setLineJoin(1)
star(canvas, "Round join", "1: rounded")
canvas.setLineJoin(2)
star(canvas, "Bevelled join", "2: square", xcenter=4.5*inch)
The line join setting is only really of interest for thick lines because it cannot be seen clearly for thin lines.
Page 35
User Guide Chapter 2 Graphics and Text with pdfgen
def caps(canvas):
from reportlab.lib.units import inch
# make lines big
canvas.setLineWidth(5)
star(canvas, "Default", "no projection",xcenter = 1*inch,
nvertices=4)
canvas.setLineCap(1)
star(canvas, "Round cap", "1: ends in half circle", nvertices=4)
canvas.setLineCap(2)
star(canvas, "Square cap", "2: projects out half a width", xcenter=4.5*inch,
nvertices=4)
The line cap setting, like the line join setting, is only clearly visible when the lines are thick.
Page 36
User Guide Chapter 2 Graphics and Text with pdfgen
def dashes(canvas):
from reportlab.lib.units import inch
# make lines big
canvas.setDash(6,3)
star(canvas, "Simple dashes", "6 points on, 3 off", xcenter = 1*inch)
canvas.setDash(1,2)
star(canvas, "Dots", "One on, two off")
canvas.setDash([1,1,3,3,1,4,4,1], 0)
star(canvas, "Complex Pattern", "[1,1,3,3,1,4,4,1]", xcenter=4.5*inch)
The patterns for the dashes or dots can be in a simple on/off repeating pattern or they can be specified in a
complex repeating pattern.
Page 37
User Guide Chapter 2 Graphics and Text with pdfgen
p.lineTo(4*u,3*u)
p.lineTo(5*u,4.5*u)
p.lineTo(3*u,6.5*u)
canvas.drawPath(p, stroke=1, fill=1)
if debug:
canvas.setStrokeColor(green) # put in a frame of reference
canvas.grid([0,5*u,10*u,15*u], [0,5*u,10*u])
Note that the interior of the pencil tip is filled as one object even though it is constructed from several lines
and curves. The pencil lead is then drawn over it using a new path object.
Page 38
User Guide Chapter 2 Graphics and Text with pdfgen
NOTE Note that this function is used to create the "margin pencil" to the left. Also note that the order in which the
elements are drawn are important because, for example, the white rectangles "erase" parts of a black
rectangle and the "tip" paints over part of the yellow rectangle.
No.2
def bezier(canvas):
from reportlab.lib.colors import yellow, green, red, black
from reportlab.lib.units import inch
i = inch
d = i/4
# define the bezier curve control points
x1,y1, x2,y2, x3,y3, x4,y4 = d,1.5*i, 1.5*i,d, 3*i,d, 5.5*i-d,3*i-d
# draw a figure enclosing the control points
canvas.setFillColor(yellow)
p = canvas.beginPath()
p.moveTo(x1,y1)
for (x,y) in [(x2,y2), (x3,y3), (x4,y4)]:
p.lineTo(x,y)
canvas.drawPath(p, fill=1, stroke=0)
# draw the tangent lines
canvas.setLineWidth(inch*0.1)
canvas.setStrokeColor(green)
canvas.line(x1,y1,x2,y2)
canvas.setStrokeColor(red)
canvas.line(x3,y3,x4,y4)
# finally draw the curve
canvas.setStrokeColor(black)
canvas.bezier(x1,y1, x2,y2, x3,y3, x4,y4)
A Bezier curve is specified by four control points (x1,y1), (x2,y2), (x3,y3), (x4,y4). The curve
starts at (x1,y1) and ends at (x4,y4) and the line segment from (x1,y1) to (x2,y2) and the line
segment from (x3,y3) to (x4,y4) both form tangents to the curve. Furthermore the curve is entirely
contained in the convex figure with vertices at the control points.
Page 39
User Guide Chapter 2 Graphics and Text with pdfgen
The drawing above (the output of testbezier) shows a bezier curves, the tangent lines defined by the
control points and the convex figure with vertices at the control points.
def bezier2(canvas):
from reportlab.lib.colors import yellow, green, red, black
from reportlab.lib.units import inch
# make a sequence of control points
xd,yd = 5.5*inch/2, 3*inch/2
xc,yc = xd,yd
dxdy = [(0,0.33), (0.33,0.33), (0.75,1), (0.875,0.875),
(0.875,0.875), (1,0.75), (0.33,0.33), (0.33,0)]
pointlist = []
for xoffset in (1,-1):
yoffset = xoffset
for (dx,dy) in dxdy:
px = xc + xd*xoffset*dx
py = yc + yd*yoffset*dy
pointlist.append((px,py))
yoffset = -xoffset
for (dy,dx) in dxdy:
px = xc + xd*xoffset*dx
py = yc + yd*yoffset*dy
pointlist.append((px,py))
# draw tangent lines and curves
canvas.setLineWidth(inch*0.1)
while pointlist:
[(x1,y1),(x2,y2),(x3,y3),(x4,y4)] = pointlist[:4]
del pointlist[:4]
canvas.setLineWidth(inch*0.1)
canvas.setStrokeColor(green)
canvas.line(x1,y1,x2,y2)
canvas.setStrokeColor(red)
canvas.line(x3,y3,x4,y4)
# finally draw the curve
canvas.setStrokeColor(black)
canvas.bezier(x1,y1, x2,y2, x3,y3, x4,y4)
The figure created by testbezier2 describes a smooth complex curve because adjacent tangent lines "line
up" as illustrated below.
Page 40
User Guide Chapter 2 Graphics and Text with pdfgen
pathobject.moveTo(x,y)
The moveTo method lifts the brush (ending any current sequence of lines or curves if there is one) and
replaces the brush at the new (x,y) location on the canvas to start a new path sequence.
pathobject.lineTo(x,y)
The lineTo method paints straight line segment from the current brush location to the new (x,y) location.
The curveTo method starts painting a Bezier curve beginning at the current brush location, using
(x1,y1), (x2,y2), and (x3,y3) as the other three control points, leaving the brush on (x3,y3).
The arc and arcTo methods paint partial ellipses. The arc method first "lifts the brush" and starts a new
shape sequence. The arcTo method joins the start of the partial ellipse to the current shape sequence by line
segment before drawing the partial ellipse. The points (x1,y1) and (x2,y2) define opposite corner points
of a rectangle enclosing the ellipse. The startAng is an angle (in degrees) specifying where to begin the
partial ellipse where the 0 angle is the midpoint of the right border of the enclosing rectangle (when
(x1,y1) is the lower left corner and (x2,y2) is the upper right corner). The extent is the angle in
degrees to traverse on the ellipse.
def arcs(canvas):
from reportlab.lib.units import inch
canvas.setLineWidth(4)
canvas.setStrokeColorRGB(0.8, 1, 0.6)
# draw rectangles enclosing the arcs
canvas.rect(inch, inch, 1.5*inch, inch)
canvas.rect(3*inch, inch, inch, 1.5*inch)
canvas.setStrokeColorRGB(0, 0.2, 0.4)
Page 41
User Guide Chapter 2 Graphics and Text with pdfgen
The arcs function above exercises the two partial ellipse methods. It produces the following drawing.
The rect method draws a rectangle with lower left corner at (x,y) of the specified width and height.
The ellipse method draws an ellipse enclosed in the rectange with lower left corner at (x,y) of the
specified width and height.
pathobject.circle(x_cen, y_cen, r)
The circle method draws a circle centered at (x_cen, y_cen) with radius r.
def variousshapes(canvas):
from reportlab.lib.units import inch
inch = int(inch)
canvas.setStrokeGray(0.5)
canvas.grid(range(0,int(11*inch/2),int(inch/2)), range(0,int(7*inch/2),int(inch/2)))
canvas.setLineWidth(4)
canvas.setStrokeColorRGB(0, 0.2, 0.7)
canvas.setFillColorRGB(1, 0.6, 0.8)
p = canvas.beginPath()
p.rect(0.5*inch, 0.5*inch, 0.5*inch, 2*inch)
p.circle(2.75*inch, 1.5*inch, 0.3*inch)
p.ellipse(3.5*inch, 0.5*inch, 1.2*inch, 2*inch)
canvas.drawPath(p, fill=1, stroke=1)
The variousshapes function above shows a rectangle, circle and ellipse placed in a frame of reference
grid.
Page 42
User Guide Chapter 2 Graphics and Text with pdfgen
pathobject.close()
The close method closes the current graphical figure by painting a line segment from the last point of the
figure to the starting point of the figure (the the most recent point where the brush was placed on the paper by
moveTo or arc or other placement operations).
def closingfigures(canvas):
from reportlab.lib.units import inch
h = inch/3.0; k = inch/2.0
canvas.setStrokeColorRGB(0.2,0.3,0.5)
canvas.setFillColorRGB(0.8,0.6,0.2)
canvas.setLineWidth(4)
p = canvas.beginPath()
for i in (1,2,3,4):
for j in (1,2):
xc,yc = inch*i, inch*j
p.moveTo(xc,yc)
p.arcTo(xc-h, yc-k, xc+h, yc+k, startAng=0, extent=60*i)
# close only the first one, not the second one
if j==1:
p.close()
canvas.drawPath(p, fill=1, stroke=1)
The closingfigures function illustrates the effect of closing or not closing figures including a line
segment and a partial ellipse.
Page 43
User Guide Chapter 2 Graphics and Text with pdfgen
Closing or not closing graphical figures effects only the stroked outline of a figure, not the filling of the
figure as illustrated above.
For a more extensive example of drawing using a path object examine the hand function.
Page 44
User Guide Chapter 2 Graphics and Text with pdfgen
In debug mode (the default) the hand function shows the tangent line segments to the bezier curves used to
compose the figure. Note that where the segments line up the curves join smoothly, but where they do not line
up the curves show a "sharp edge".
Used in non-debug mode the hand function only shows the Bezier curves. With the fill parameter set the
figure is filled using the current fill color.
def hand2(canvas):
canvas.translate(20,10)
canvas.setLineWidth(3)
canvas.setFillColorRGB(0.1, 0.3, 0.9)
canvas.setStrokeGray(0.5)
hand(canvas, debug=0, fill=1)
Note that the "stroking" of the border draws over the interior fill where they overlap.
Page 45
User Guide Chapter 2 Graphics and Text with pdfgen
Page 46
User Guide Chapter 3 Fonts and encodings
The simplest fix is just to convert your data to unicode, saying which encoding it comes from, like this:
Page 47
User Guide Chapter 3 Fonts and encodings
import os
import reportlab
folder = os.path.dirname(reportlab.__file__) + os.sep + 'fonts'
afmFile = os.path.join(folder, 'DarkGardenMK.afm')
pfbFile = os.path.join(folder, 'DarkGardenMK.pfb')
canvas.setFont('DarkGardenMK', 32)
canvas.drawString(10, 150, 'This should be in')
canvas.drawString(10, 100, 'DarkGardenMK')
Note that the argument "WinAnsiEncoding" has nothing to do with the input; it's to say which set of
characters within the font file will be active and available.
This should be in
DarkGardenMK
Page 48
User Guide Chapter 3 Fonts and encodings
The font's facename comes from the AFM file's FontName field. In the example above we knew the name in
advance, but quite often the names of font description files are pretty cryptic and then you might want to
retrieve the name from an AFM file automatically. When lacking a more sophisticated method you can use
some code as simple as this:
class FontNameNotFoundError(Exception):
pass
def findFontName(path):
"Extract a font name from an AFM file."
f = open(path)
found = 0
while not found:
line = f.readline()[:-1]
if not found and line[:16] == 'StartCharMetrics':
raise FontNameNotFoundError, path
if line[:8] == 'FontName':
fontName = line[9:]
found = 1
return fontName
In the DarkGardenMK example we explicitely specified the place of the font description files to be loaded. In
general, you'll prefer to store your fonts in some canonic locations and make the embedding mechanism
aware of them. Using the same configuration mechanism we've already seen at the beginning of this section
we can indicate a default search path for Type-1 fonts.
Unfortunately, there is no reliable standard yet for such locations (not even on the same platform) and, hence,
you might have to edit the file reportlab/rl_config.py to modify the value of the T1SearchPath
identifier to contain additional directories. Our own recommendation is to use the reportlab/fonts
folder in development; and to have any needed fonts as packaged parts of your application in any kind of
controlled server deployment. This insulates you from fonts being installed and uninstalled by other software
or system administrator.
import reportlab.rl_config
reportlab.rl_config.warnOnMissingFontGlyphs = 0
Page 49
User Guide Chapter 3 Fonts and encodings
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
00
The code chart below shows the characters in the MacRomanEncoding. as it sounds, this is the standard
encoding on Macintosh computers in America and Western Europe. As usual with non-unicode encodings,
the first 128 code points (top 4 rows in this case) are the ASCII standard and agree with the WinAnsi code
chart above; but the bottom 4 rows differ.
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
00
These two encodings are available for the standard fonts (Helvetica, Times-Roman and Courier and their
variants) and will be available for most commercial fonts including those from Adobe. However, some fonts
contain non- text glyphs and the concept does not really apply. For example, ZapfDingbats and Symbol can
each be treated as having their own encoding.
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
00
20
40
60
80
A0
C0
E0
00
Page 50
User Guide Chapter 3 Fonts and encodings
In the above example the true type font object is created using
TTFont(name,filename)
so that the ReportLab internal name is given by the first argument and the second argument is a string(or file
like object) denoting the font's TTF file. In Marius' original patch the filename was supposed to be exactly
correct, but we have modified things so that if the filename is relative then a search for the corresponding file
is done in the current directory and then in directories specified by
reportlab.rl_config.TTFSearchpath!
Before using the TT Fonts in Platypus we should add a mapping from the family name to the individual font
names that describe the behaviour under the <b> and <i> attributes.
If we only have a Vera regular font, no bold or italic then we must map all to the same internal fontname. <b>
and <i> tags may now be used safely, but have no effect. After registering and mapping the Vera font as
Page 51
User Guide Chapter 3 Fonts and encodings
<font name="Times-Roman"
size="14">This is in This is in Times-Roman
Times-Roman</font> <font and this is in magenta
name="Vera" color="magenta" Vera!
size="14">and this is in magenta
<b>Vera!</b></font>
The old coding style with explicit encodings should still work, but is now only relevant if you need to
construct vertical text. We aim to add more readable options for horizontal and vertical text to the
UnicodeCIDFont constructor in future. The following four test scripts generate samples in the corresponding
languages:
tests/test_multibyte_jpn.py
tests/test_multibyte_kor.py
tests/test_multibyte_chs.py
tests/test_multibyte_cht.py
In previous versions of the ReportLab PDF Library, we had to make use of Adobe's CMap files (located near
Acrobat Reader if the Asian Language packs were installed). Now that we only have one encoding to deal
with, the character width data is embedded in the package, and CMap files are not needed for generation. The
CMap search path in rl_config.py is now deprecated and has no effect if you restrict yourself to
Page 52
User Guide Chapter 3 Fonts and encodings
UnicodeCIDFont.
To Do
We expect to be developing this area of the package for some time.accept2dyear Here is an outline of the
main priorities. We welcome help!
Ensure that we have accurate character metrics for all encodings in horizontal and vertical
writing.
Add options to UnicodeCIDFont to allow vertical and proportional variants where the font
permits it.
Improve the word wrapping code in paragraphs and allow vertical writing.
C:\code\reportlab\graphics>renderPM.py
wrote pmout\renderPM0.gif
wrote pmout\renderPM0.tif
wrote pmout\renderPM0.png
wrote pmout\renderPM0.jpg
wrote pmout\renderPM0.pct
...
wrote pmout\renderPM12.gif
wrote pmout\renderPM12.tif
wrote pmout\renderPM12.png
wrote pmout\renderPM12.jpg
wrote pmout\renderPM12.pct
wrote pmout\index.html
This runs a number of tests progressing from a "Hello World" test, through various tests of Lines; text strings
in a number of sizes, fonts, colours and alignments; the basic shapes; translated and rotated groups; scaled
coordinates; rotated strings; nested groups; anchoring and non-standard fonts.
It creates a subdirectory called pmout, writes the image files into it, and writes an index.html page which
makes it easy to refer to all the results.
The font-related tests which you may wish to look at are test #11 ('Text strings in a non-standard font') and
test #12 ('Test Various Fonts').
Page 53
User Guide Chapter 4 Exposing PDF Special Capabilities
4.1 Forms
The Form feature lets you create a block of graphics and text once near the start of a PDF file, and then
simply refer to it on subsequent pages. If you are dealing with a run of 5000 repetitive business forms - for
example, one-page invoices or payslips - you only need to store the backdrop once and simply draw the
changing text on each page. Used correctly, forms can dramatically cut file size and production time, and
apparently even speed things up on the printer.
Forms do not need to refer to a whole page; anything which might be repeated often should be placed in a
form.
The example below shows the basic sequence used. A real program would probably define the forms up front
and refer to them from another location.
def forms(canvas):
#first create a form...
canvas.beginForm("SpumoniForm")
#re-use some drawing functions from earlier
spumoni(canvas)
canvas.endForm()
#then draw it
canvas.doForm("SpumoniForm")
canvas.bookmarkPage(name,
fit="Fit",
left=None,
top=None,
bottom=None,
right=None,
zoom=None
)
By default the bookmarkPage method defines the page itself as the destination. After jumping to an
endpoint defined by bookmarkPage, the PDF browser will display the whole page, scaling it to fit the screen:
canvas.bookmarkPage(name)
The bookmarkPage method can be instructed to display the page in a number of different ways by
providing a fit parameter.
Page 54
User Guide Chapter 4 Exposing PDF Special Capabilities
canvas.bookmarkPage('my_bookmark',fit="XYZ",left=0,top=200)
This destination is at the leftmost of the page with the top of the screen at position 200. Because zoom was
not set the zoom remains at whatever the user had it set to.
canvas.bookmarkPage('my_bookmark',fit="XYZ",left=0,top=200,zoom=2)
This time zoom is set to expand the page 2X its normal size.
Note : Both XYZ and FitR fit types require that their positional parameters (top, bottom, left,
right) be specified in terms of the default user space. They ignore any geometric transform in effect in the
canvas graphic state.
NOTE Note: Two previous bookmark methods are supported but deprecated now that bookmarkPage is so general.
These are bookmarkHorizontalAbsolute and bookmarkHorizontal.
The linkAbsolute method defines a starting point for a jump. When the user is browsing the generated
document using a dynamic viewer (such as Acrobat Reader) when the mouse is clicked when the pointer is
within the rectangle specified by Rect the viewer will jump to the endpoint associated with
destinationname. As in the case with bookmarkHorizontalAbsolute the rectangle Rect must
be specified in terms of the default user space. The contents parameter specifies a chunk of text which
displays in the viewer if the user left-clicks on the region.
The rectangle Rect must be specified in terms of a tuple (x1,y1,x2,y2) identifying the lower left and
upper right points of the rectangle in default user space.
For example the code
canvas.bookmarkPage("Meaning_of_life")
defines a location as the whole of the current page with the identifier Meaning_of_life. To create a
rectangular link to it while drawing a possibly different page, we would use this code:
By default during interactive viewing a rectangle appears around the link. Use the keyword argument
Border='[0 0 0]' to suppress the visible rectangle around the during viewing link. For example
The thickness, color and dashArray arguments may be used alternately to specify a border if no
Border argument is specified. If Border is specified it must be either a string representation of a PDF array or
a PDFArray (see the pdfdoc module). The color argument (which should be a Color instance) is
equivalent to a keyword argument C which should resolve to a PDF color definition (Normally a three entry
PDF array).
Page 55
User Guide Chapter 4 Exposing PDF Special Capabilities
The canvas.linkRect method is similar in intent to the linkAbsolute method, but has an extra
argument relative=1 so is intended to obey the local userspace transformation.
The setPageTransition method specifies how one page will be replaced with the next. By setting the
page transition effect to "dissolve" for example the current page will appear to melt away when it is replaced
by the next page during interactive viewing. These effects are useful in spicing up slide presentations, among
other places. Please see the reference manual for more detail on how to use this method.
canvas.setAuthor(name)
canvas.setTitle(title)
canvas.setSubject(subj)
These methods have no automatically seen visible effect on the document. They add internal annotations to
the document. These annotations can be viewed using the "Document Info" menu item of the browser and
they also can be used as a simple standard way of providing basic information about the document to
archiving software which need not parse the entire file. To find the annotations view the *.pdf output file
using a standard text editor (such as notepad on MS/Windows or vi or emacs on unix) and look for the
string /Author in the file contents.
def annotations(canvas):
from reportlab.lib.units import inch
canvas.drawString(inch, 2.5*inch,
"setAuthor, setTitle, setSubject have no visible effect")
canvas.drawString(inch, inch, "But if you are viewing this document dynamically")
canvas.drawString(inch, 0.5*inch, "please look at File/Document Info")
canvas.setAuthor("the ReportLab Team")
canvas.setTitle("ReportLab PDF Generation User Guide")
canvas.setSubject("How to Generate PDF files using the ReportLab modules")
Page 56
User Guide Chapter 4 Exposing PDF Special Capabilities
If you want the subject, title, and author to automatically display in the document when viewed and printed
you must paint them onto the document like any other text.
4.6 Encryption
Page 57
User Guide Chapter 4 Exposing PDF Special Capabilities
When a PDF file is encrypted, encryption is applied to all the strings and streams in the file. This prevents
people who don't have the password from simply removing the password from the PDF file to gain access to
it - it renders the file useless unless you actually have the password.
PDF's standard encryption methods use the MD5 message digest algorithm (as described in RFC 1321, The
MD5 Message-Digest Algorithm) and an encryption algorithm known as RC4. RC4 is a symmetric stream
cipher - the same algorithm is used both for encryption and decryption, and the algorithm does not change the
length of the data.
The userPassword and ownerPassword parameters set the relevant password on the encrypted PDF.
The boolean flags canPrint, canModify, canCopy, canAnnotate determine wether a user can
perform the corresponding actions on the PDF when only a user password has been supplied.
If the user supplies the owner password while opening the PDF, all actions can be performed regardless of
the flags.
Example
To create a document named hello.pdf with a user password of 'rptlab' on which printing is not allowed, use
the following code:
enc=pdfencrypt.StandardEncryption("rptlab",canPrint=0)
def hello(c):
c.drawString(100,100,"Hello World")
c = canvas.Canvas("hello.pdf",encrypt=enc)
hello(c)
c.showPage()
c.save()
Page 58
User Guide Chapter 4 Exposing PDF Special Capabilities
Example
This shows the basic mechanism of creating an interactive element on the current page.
canvas.acroform.checkbox(
name='CB0',
tooltip='Field CB0',
checked=True,
x=72,y=72+4*36,
buttonStyle='diamond',
borderStyle='bevelled',
borderWidth=2,
borderColor=red,
fillColor=green,
textColor=blue,
forceBorder=True)
NB note that the acroform canvas property is created automatically on demand and that there is only one form
allowd in a document.
Checkbox Usage
The canvas.acroform.checkbox method creates a checkbox widget on the current page. The value of the
checkbox is either YES or OFF. The arguments are
canvas.acroform.checkbox parameters
Parameter Meaning Default
name the parameter's name None
borderWidth as it says 1
tooltip The text to display when hovering over the widget None
Page 59
User Guide Chapter 4 Exposing PDF Special Capabilities
Radio Usage
The canvas.acroform.radio method creates a radio widget on the current page. The value of the radio is the
value of the radio group's selected value or OFF if none are selected. The arguments are
canvas.acroform.radio parameters
Parameter Meaning Default
name the radio's group (ie parameter) name None
selected if True this radio is the selected one in its group False
borderWidth as it says 1
tooltip The text to display when hovering over the widget None
fieldFlags Blank separated field flags (see below) 'noToggleToOff required radio'
Listbox Usage
The canvas.acroform.listbox method creates a listbox widget on the current page. The listbox contains a list
of options one or more of which (depending on fieldFlags) may be selected.
canvas.acroform.listbox parameters
Parameter Meaning Default
name the radio's group (ie parameter) name None
borderWidth as it says 1
tooltip The text to display when hovering over the widget None
Page 60
User Guide Chapter 4 Exposing PDF Special Capabilities
canvas.acroform.listbox parameters
Parameter Meaning Default
forceBorder when true a border force a border to be drawn False
Choice Usage
The canvas.acroform.choice method creates a dropdown widget on the current page. The dropdown contains
a list of options one or more of which (depending on fieldFlags) may be selected. If you add edit to the
fieldFlags then the result may be edited.
canvas.acroform.choice parameters
Parameter Meaning Default
name the radio's group (ie parameter) name None
borderWidth as it says 1
tooltip The text to display when hovering over the widget None
Textfield Usage
The canvas.acroform.textfield method creates a textfield entry widget on the current page. The textfield may
be edited to change tha value of the widget
canvas.acroform.textfield parameters
Parameter Meaning Default
name the radio's group (ie parameter) name None
Page 61
User Guide Chapter 4 Exposing PDF Special Capabilities
canvas.acroform.textfield parameters
Parameter Meaning Default
fontSize The size of font to be used 12
borderWidth as it says 1
tooltip The text to display when hovering over the widget None
Button styles
The button style argument indicates what style of symbol should appear in the button when it is selected.
There are several choices
check
cross
circle
star
diamond
note that the document renderer can make some of these symbols wrong for their intended application.
Acrobat reader prefers to use its own rendering on top of what the specification says should be shown
(especially when the forms hihlighting features are used
Widget shape
The shape argument describes how the outline of the checkbox or radio widget should appear you can use
circle
square
The renderer may make its own decisions about how the widget should look; so Acrobat Reader prefers
circular outlines for radios.
Border style
The borderStyle argument changes the 3D appearance of the widget on the page alternatives are
solid
dashed
inset
bevelled
underlined
fieldFlags Argument
The fieldFlags arguments can be an integer or a string containing blank separate tokens the values are shown
in the table below. For more information consult the PDF specification.
Field Flag Tokens and values
Token Meaning Value
readOnly The widget is read only 1<<0
Page 62
User Guide Chapter 4 Exposing PDF Special Capabilities
comb make a comb style text based on the maxlen value 1<<24
annotationFlags Argument
PDF widgets are annotations and have annotation properties these are shown in the table below
Annotation Flag Tokens and values
Token Meaning Value
invisible The widget is not shown 1<<0
nozoom The annotation will notscale with the rendered page 1<<3
Page 63
User Guide Chapter 5 PLATYPUS - Page Layout and Typography Using Scripts
DocTemplate
flowable 155
First Flowable
right Frame
flowable 156
left Frame
flowable 157
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 Flowables to its
build method. The document build method knows how to process the list of flowables into something
reasonable.
Page 64
User Guide Chapter 5 PLATYPUS - Page Layout and Typography Using Scripts
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.
First we import some constructors, some paragraph styles and other conveniences from other modules.
We define the fixed features of the first page of the document with the function above.
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 module docs/userguide/examples.py (from the
ReportLab docs distribution) as a "top level script". The script interpretation python examples.py will
generate the Platypus output phello.pdf.
Page 65
User Guide Chapter 5 PLATYPUS - Page Layout and Typography Using Scripts
5.3 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.
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.
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.
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.
Page 66
User Guide Chapter 5 PLATYPUS - Page Layout and Typography Using Scripts
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.
5.5 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
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.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)
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.
Page 67
User Guide Chapter 5 PLATYPUS - Page Layout and Typography Using Scripts
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.
BaseDocTemplate.addPageTemplates(self,pageTemplates)
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.
BaseDocTemplate.afterInit(self)
This is called after initialisation of the base class; a derived class could overide the method to add default
PageTemplates.
Page 68
User Guide Chapter 5 PLATYPUS - Page Layout and Typography Using Scripts
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.
def handle_pageBegin(self):
doStuff()
BaseDocTemplate.handle_pageBegin(self)
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.
Page 69
User Guide Chapter 5 PLATYPUS - Page Layout and Typography Using Scripts
styleN = styles['Normal']
styleH = styles['Heading1']
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.
Page 70
User Guide Chapter 6 Paragraphs
Chapter 6 Paragraphs
The reportlab.platypus.Paragraph class is one of the most useful of the Platypus Flowables; it
can format fairly arbitrary text and provides for inline font style and colour changes using an XML style
markup. The overall shape of the formatted text can be justified, right or left ragged or centered. The XML
markup can even be used to insert greek characters or to do subscripts.
The following text creates an instance of the Paragraph class:
The text argument contains the text of the paragraph; excess white space is removed from the text at the
ends and internally after linefeeds. This allows easy use of indented triple quoted text in Python scripts. The
bulletText argument provides the text of a default bullet for the paragraph. The font and other properties
for the paragraph text and bullet are set using the style argument.
The style argument should be an instance of class ParagraphStyle obtained typically using
this container class provides for the setting of multiple default paragraph attributes in a structured way. The
styles are arranged in a dictionary style object called a stylesheet which allows for the styles to be
accessed as stylesheet['BodyText']. A sample style sheet is provided.
The options which can be set for a Paragraph can be seen from the ParagraphStyle defaults.
class ParagraphStyle
class ParagraphStyle(PropertySet):
defaults = {
'fontName':_baseFontName,
'fontSize':10,
'leading':12,
'leftIndent':0,
'rightIndent':0,
'firstLineIndent':0,
'alignment':TA_LEFT,
'spaceBefore':0,
'spaceAfter':0,
'bulletFontName':_baseFontName,
'bulletFontSize':10,
'bulletIndent':0,
'textColor': black,
'backColor':None,
'wordWrap':None,
'borderWidth': 0,
'borderPadding': 0,
'borderColor': None,
'borderRadius': None,
'allowWidows': 1,
'allowOrphans': 0,
'textTransform':None,
'endDots':None,
'splitLongWords':1,
'underlineProportion': _baseUnderlineProportion,
'bulletAnchor': 'start',
'justifyLastLine': 0,
'justifyBreaks': 0,
'spaceShrinkage': spaceShrinkage,
}
Page 71
User Guide Chapter 6 Paragraphs
The two attributes spaceBefore and spaceAfter do what they say, except at the top or bottom of a
frame. At the top of a frame, spaceBefore is ignored, and at the bottom, spaceAfter is ignored. This
means that you could specify that a 'Heading2' style had two inches of space before when it occurs in
mid-page, but will not get acres of whitespace at the top of a page. These two attributes should be thought of
as 'requests' to the Frame and are not part of the space occupied by the Paragraph itself.
The fontSize and fontName tags are obvious, but it is important to set the leading. This is the spacing
between adjacent lines of text; a good rule of thumb is to make this 20% larger than the point size. To get
double-spaced text, use a high leading. If you set autoLeading(default "off") to "min"(use
observed leading even if smaller than specified) or "max"(use the larger of observed and specified) then an
attempt is made to determine the leading on a line by line basis. This may be useful if the lines contain
different font sizes etc.
The figure below shows space before and after and an increased leading:
Page 72
User Guide Chapter 6 Paragraphs
alignment = 0
allowOrphans = 0
You are hereby charged that on the
allowWidows = 1
backColor = None 28th day of May, 1970, you did willfully,
borderColor = None
borderPadding = 0 unlawfully, and with malice of
borderRadius = None
forethought, publish an alleged
borderWidth = 0
bulletAnchor = start English-Hungarian phrase book with
bulletFontName = Helvetica
bulletFontSize = 10 intent to cause a breach of the peace.
bulletIndent = 0
How do you plead?
endDots = None
firstLineIndent = 0
fontName = Helvetica
fontSize = 10
justifyBreaks = 0
justifyLastLine = 0
leading = 16
leftIndent = 0
rightIndent = 0
spaceAfter = 6
spaceBefore = 6
spaceShrinkage = 0.05
splitLongWords = 1
textColor = Color(0,0,0,1)
textTransform = None
underlineProportion = 0.0
wordWrap = None
The attribute borderPadding adjusts the padding between the paragraph and the border of its background.
This can either be a single value or a tuple containing 2 to 4 values. These values are applied the same way as
in Cascading Style Sheets (CSS). If a single value is given, that value is applied to all four sides. If more than
one value is given, they are applied in clockwise order to the sides starting at the top. If two or three values
are given, the missing values are taken from the opposite side(s). Note that in the following example the
yellow box is drawn by the paragraph itself.
Page 73
User Guide Chapter 6 Paragraphs
The leftIndent and rightIndent attributes do exactly what you would expect; firstLineIndent
is added to the leftIndent of the first line. If you want a straight left edge, remember to set
firstLineIndent equal to 0.
Page 74
User Guide Chapter 6 Paragraphs
Figure 6-4: one third inch indents at left and right, two thirds on first line
Setting firstLineIndent equal to a negative number, leftIndent much higher, and using a different
font (we'll show you how later!) can give you a definition list:.
Page 75
User Guide Chapter 6 Paragraphs
There are four possible values of alignment, defined as constants in the module reportlab.lib.enums.
These are TA_LEFT, TA_CENTER or TA_CENTRE, TA_RIGHT and TA_JUSTIFY, with values of 0, 1, 2
and 4 respectively. These do exactly what you would expect.
Set wordWrap to 'CJK' to get Asian language linewrapping. For normal western text you can change the
way the line breaking algorithm handles widows and orphans with the allowWidows and allowOrphans
values. Both should normally be set to 0, but for historical reasons we have allowed widows. The default
color of the text can be set with textColor and the paragraph background colour can be set with
backColor. The paragraph's border properties may be changed using borderWidth, borderPadding,
borderColor and borderRadius.
The textTransform attribute can be None, 'upper' or 'lower' to get the obvious result.
Attribute endDots can be None, a string, or an object with attributes text and optional fontName, fontSize,
textColor, backColor and dy(y offset) to specify trailing matter on the last line of left/right justified
paragraphs.
The splitLongWords attribute can be set to a false value to avoid splitting very long words.
The underLineProportion attribute can be set to a true or false value to control whether underlines are
proportional to the font size.
Attribute bulletAnchor can be 'start', 'middle', 'end' or 'numeric' to control where the bullet is anchored.
Page 76
User Guide Chapter 6 Paragraphs
Attribute Synonyms
alignment align, alignment
allowOrphans allowOrphans, alloworphans
allowWidows allowWidows, allowwidows
autoLeading autoLeading, autoleading
backColor backColor, backcolor, bg, bgcolor
borderColor borderColor, bordercolor
borderRadius borderRadius, borderradius
borderWidth borderWidth, borderwidth
borderpadding borderpadding
bulletAnchor banchor, bulletAnchor, bulletanchor
bulletColor bcolor, bulletColor, bulletcolor
bulletFontName bfont, bulletFontName, bulletfontname
bulletFontSize bfontsize, bulletFontSize, bulletfontsize
bulletIndent bindent, bulletIndent, bulletindent
bulletOffsetY boffsety, bulletOffsetY, bulletoffsety
endDots endDots, enddots
firstLineIndent findent, firstLineIndent, firstlineindent
fontName face, font, fontName, fontname
fontSize fontSize, fontsize, size
justifyBreaks justifyBreaks, justifybreaks
justifyLastLine justifyLastLine, justifylastline
leading leading
leftIndent leftIndent, leftindent, lindent
rightIndent rightIndent, rightindent, rindent
spaceAfter spaceAfter, spacea, spaceafter
spaceBefore spaceBefore, spaceb, spacebefore
spaceShrinkage spaceShrinkage, spaceshrinkage
splitLongWords splitLongWords, splitlongwords
textColor color, fg, textColor, textcolor
textTransform textTransform, texttransform
underlineProportion underlineProportion, underlineproportion
wordWrap wordWrap, wordwrap
Page 77
User Guide Chapter 6 Paragraphs
<b>You are hereby charged</b> You are hereby charged that on the
that on the 28th day of May,
28th day of May, 1970, you did
1970, you did willfully,
unlawfully, and <i>with malice of willfully, unlawfully, and with malice
forethought</i>, publish an of forethought, publish an alleged
alleged English-Hungarian phrase English-Hungarian phrase book with
book with intent to cause a
intent to cause a breach of the peace.
breach of the peace. <u>How do
you plead</u>? How do you plead?
The link tag can be used as a reference, but not as an anchor. The a and link hyperlink tags have additional
attributes fontName, fontSize, color & backColor attributes. The hyperlink reference can have a scheme of
http:(external webpage), pdf:(different pdf document) or document:(same pdf document); a missing scheme
is treated as document as is the case when the reference starts with # (in which case the anchor should omit
it). Any other scheme is treated as some kind of URI.
Page 78
User Guide Chapter 6 Paragraphs
Inline Images
We can embed images in a paragraph with the <img/> tag which has attributes src, width, height whose
meanings are obvious. The valign attribute may be set to a css like value from "baseline", "sub", "super",
"top", "text-top", "middle", "bottom", "text-bottom"; the value may also be a numeric percentage or an
absolute value.
<para autoLeading="off"
fontSize=12>This <img/>
This <img/> is aligned top.
<img src="../images/testimg.gif"
valign="top"/> is aligned This <img/> is aligned
<b>top</b>.<br/><br/> This bottom.
<img/> <img
src="../images/testimg.gif"
valign="bottom"/> is aligned This <img/> is aligned
<b>bottom</b>.<br/><br/> This middle.
<img/> <img
src="../images/testimg.gif"
valign="middle"/> is aligned
This <img/> is aligned -4.
<b>middle</b>.<br/><br/> This
<img/> <img This <img/> is aligned +4.
src="../images/testimg.gif"
valign="-4"/> is aligned
<b>-4</b>.<br/><br/> This
This <img/> has width 10.
<img/> <img
src="../images/testimg.gif"
valign="+4"/> is aligned
<b>+4</b>.<br/><br/> This
<img/> <img
src="../images/testimg.gif"
width="10"/> has width
<b>10</b>.<br/><br/> </para>
Page 79
User Guide Chapter 6 Paragraphs
You can save specifying an ID by designating a counter ID as the default using the <seqdefault
id="Counter"> tag; it will then be used whenever a counter ID is not specified. This saves some typing,
especially when doing multi-level lists; you just change counter ID when stepping in or out a level.
Finally, one can access multi-level sequences using a variation of Python string formatting and the
template attribute in a <seq> tags. This is used to do the captions in all of the figures, as well as the level
two headings. The substring %(counter)s extracts the current value of a counter without incrementing it;
appending a plus sign as in %(counter)s increments the counter. The figure captions use a pattern like the
one below:
We cheated a little - the real document used 'Figure', but the text above uses 'FigureNo' - otherwise we would
have messed up our numbering!
Page 80
User Guide Chapter 6 Paragraphs
Exactly the same technique is used for numbers, except that a sequence tag is used. It is also possible to put a
multi-character string in the bullet; with a deep indent and bold bullet font, you can make a compact
definition list.
Page 81
User Guide Chapter 7 Tables and TableStyles
Page 82
User Guide Chapter 7 Tables and TableStyles
too little space is available in the current drawing area and the caller wants the Table to split. Splitting a
Table by column is currently not implemented, so setting splitByRow to False will result in a
NotImplementedError.
The repeatRows argument specifies the number or a tuple of leading rows that should be repeated when
the Table is asked to split itself. If it is a tuple it should specify which of the leading rows should be
repeated; this allows for cases where the first appearance of the table hsa more leading rows than later split
parts. The repeatCols argument is currently ignored as a Table cannot be split by column.
The spaceBefore & spaceAfter arguments may be used to put extra space before or after the table
when renedered in a platypus story.
The rowSplitRange argument may be used to control the splitting of the table to a subset of its rows; that
can be to prevent splitting too close to the beginning or end of the table.
Table.setStyle(tblStyle)
This method applies a particular instance of class TableStyle (discussed below) to the Table instance.
This is the only way to get tables to appear in a nicely formatted way.
Successive uses of the setStyle method apply the styles in an additive fashion. That is, later applications
override earlier ones where they overlap.
7.2 TableStyle
This class is created by passing it a sequence of commands, each command is a tuple identified by its first
element which is a string; the remaining elements of the command tuple represent the start and stop cell
coordinates of the command and possibly thickness and colors, etc.
TableStyle(commandSequence)
The creation method initializes the TableStyle with the argument command sequence as an example:
LIST_STYLE = TableStyle(
[('LINEABOVE', (0,0), (-1,0), 2, colors.green),
('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black),
('LINEBELOW', (0,-1), (-1,-1), 2, colors.green),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]
)
TableStyle.add(commandSequence)
This method allows you to add commands to an existing TableStyle, i.e. you can build up
TableStyles in multiple statements.
TableStyle.getCommands()
This method returns the sequence of commands of the instance.
cmds = LIST_STYLE.getCommands()
Page 83
User Guide Chapter 7 Tables and TableStyles
limit values as in Python indexing. The coordinates are given as (column, row) which follows the spreadsheet
'A1' model, but not the more natural (for mathematicians) 'RC' ordering. The top left cell is (0, 0) the bottom
right is (-1, -1). Depending on the command various extra (???) occur at indices beginning at 3 on.
This sets the background cell color in the relevant cells. The following example shows the BACKGROUND,
and TEXTCOLOR commands in action:
produces
00 01 02 03 04
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34
To see the effects of the alignment styles we need some widths and a grid, but it should be easy to see where
the styles come from.
produces
00
01 02 03 04
Page 84
User Guide Chapter 7 Tables and TableStyles
10
11 12 13 14
20
21 22 23 24
30 31 32 33 34
produces
00 01 02 03 04
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34
Line commands cause problems for tables when they split; the following example shows a table being split in
various positions
produces
00 01 02 03 04
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34
00 01 02 03 04
Page 85
User Guide Chapter 7 Tables and TableStyles
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34
00 01 02 03 04
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34
I = Image('../images/replogo.gif')
I.drawHeight = 1.25*inch*I.drawHeight / I.drawWidth
I.drawWidth = 1.25*inch
P0 = Paragraph('''
<b>A pa<font color=red>r</font>a<i>graph</i></b>
<super><font color=yellow>1</font></super>''',
styleSheet["BodyText"])
P = Paragraph('''
<para align=center spaceb=3>The <b>ReportLab Left
<font color=red>Logo</font></b>
Image</para>''',
styleSheet["BodyText"])
data= [['A', 'B', 'C', P0, 'D'],
['00', '01', '02', [I,P], '04'],
['10', '11', '12', [P,I], '14'],
['20', '21', '22', '23', '24'],
['30', '31', '32', '33', '34']]
t=Table(data,style=[('GRID',(1,1),(-2,-2),1,colors.green),
('BOX',(0,0),(1,-1),2,colors.red),
('LINEABOVE',(1,2),(-2,2),1,colors.blue),
('LINEBEFORE',(2,1),(2,-2),1,colors.pink),
('BACKGROUND', (0, 0), (0, 1), colors.pink),
('BACKGROUND', (1, 1), (1, 2), colors.lavender),
('BACKGROUND', (2, 2), (2, 3), colors.orange),
('BOX',(0,0),(-1,-1),2,colors.black),
('GRID',(0,0),(-1,-1),0.5,colors.black),
('VALIGN',(3,0),(3,0),'BOTTOM'),
('BACKGROUND',(3,0),(3,0),colors.limegreen),
('BACKGROUND',(3,1),(3,1),colors.khaki),
('ALIGN',(3,1),(3,1),'CENTER'),
('BACKGROUND',(3,2),(3,2),colors.beige),
('ALIGN',(3,2),(3,2),'LEFT'),
])
t._argW[3]=1.5*inch
produces
A B C A paragraph 1 D
Page 86
User Guide Chapter 7 Tables and TableStyles
10 11 12 14
20 21 22 23 24
30 31 32 33 34
indicates that the cells in columns sc - ec and rows sr - er should be combined into a super cell with
contents determined by the cell (sc, sr). The other cells should be present, but should contain empty
strings or you may get unexpected results.
produces
02 03 04
Top
Left 12 13 14
20 21 22
Bottom
30 31 32 Right
notice that we don't need to be conservative with our GRID command. The spanned cells are not drawn
through.
demands that the cells in columns sc - ec and rows sr - er may not be split.
Page 87
User Guide Chapter 7 Tables and TableStyles
Page 88
User Guide Chapter 8 Programming Flowables
DocAssign('i',3)
DocExec('i-=1')
DocAssign('i',5)
DocWhile('i',[DocPara('i',format='The value of i is %(__expr__)d',style=normal),DocExec('i-=1')])
The value of i is 5
The value of i is 4
The value of i is 3
The value of i is 2
The value of i is 1
Page 89
User Guide Chapter 9 Other Useful Flowables
produces
class XPreformatted(Paragraph):
def __init__(self, text, style, bulletText = None,
> frags=None, caseSensitive=1):
self.caseSensitive = caseSensitive
if maximumLineLength and text:
text = self.stopLine(text, maximumLineLength,
> splitCharacters)
cleaner = lambda text, dedent=dedent: ''.join(
> _dedenter(text or '',dedent))
self._setup(text, style, bulletText, frags, cleaner)
Page 90
User Guide Chapter 9 Other Useful Flowables
'''
t=XPreformatted(text,normalStyle,dedent=3)
produces
This is a non rearranging form of the Paragraph class;
XML tags are allowed in text and have the same
Image("lj8100.jpg")
will display as
whereas
produces
Page 91
User Guide Chapter 9 Other Useful Flowables
9.5 PageBreak()
This Flowable represents a page break. It works by effectively consuming all vertical space given to it.
This is sufficient for a single Frame document, but would only be a frame break for multiple frames so the
BaseDocTemplate mechanism detects pageBreaks internally and handles them specially.
9.6 CondPageBreak(height)
This Flowable attempts to force a Frame break if insufficient vertical space remains in the current Frame.
It is thus probably wrongly named and should probably be renamed as CondFrameBreak.
9.7 KeepTogether(flowables)
This compound Flowable takes a list of Flowables and attempts to keep them in the same Frame. If the
total height of the Flowables in the list flowables exceeds the current frame's available space then all
the space is used and a frame break is forced.
9.8 TableOfContents()
A table of contents can be generated by using the TableOfContents flowable. The following steps are
needed to add a table of contents to your document:
Create an instance of TableOfContents. Override the level styles (optional) and add the object to the
story:
toc = TableOfContents()
PS = ParagraphStyle
toc.levelStyles = [
PS(fontName='Times-Bold', fontSize=14, name='TOCHeading1',
leftIndent=20, firstLineIndent=-20, spaceBefore=5, leading=16),
PS(fontSize=12, name='TOCHeading2',
leftIndent=40, firstLineIndent=-20, spaceBefore=0, leading=12),
PS(fontSize=10, name='TOCHeading3',
leftIndent=60, firstLineIndent=-20, spaceBefore=0, leading=12),
PS(fontSize=10, name='TOCHeading4',
leftIndent=100, firstLineIndent=-20, spaceBefore=0, leading=12),
]
story.append(toc)
Entries to the table of contents can be done either manually by calling the addEntry method on the
TableOfContents object or automatically by sending a 'TOCEntry' notification in the
afterFlowable method of the DocTemplate you are using. The data to be passed to notify is a list
of three or four items countaining a level number, the entry text, the page number and an optional destination
key which the entry should point to. This list will usually be created in a document template's method like
afterFlowable(), making notification calls using the notify() method with appropriate data like this:
Page 92
User Guide Chapter 9 Other Useful Flowables
This way, whenever a paragraph of style 'Heading1' or 'Heading2' is added to the story, it will appear
in the table of contents. Heading2 entries will be clickable because a bookmarked key has been supplied.
Finally you need to use the multiBuild method of the DocTemplate because tables of contents need
several passes to be generated:
doc.multiBuild(story)
class MyDocTemplate(BaseDocTemplate):
h1 = PS(name = 'Heading1',
fontSize = 14,
leading = 16)
h2 = PS(name = 'Heading2',
fontSize = 12,
leading = 14,
leftIndent = delta)
# Build story.
story = []
toc = TableOfContents()
# For conciseness we use the same styles for headings and TOC entries
toc.levelStyles = [h1, h2]
story.append(toc)
story.append(PageBreak())
story.append(Paragraph('First heading', h1))
story.append(Paragraph('Text in first heading', PS('body')))
story.append(Paragraph('First sub heading', h2))
story.append(Paragraph('Text in first sub heading', PS('body')))
story.append(PageBreak())
story.append(Paragraph('Second sub heading', h2))
story.append(Paragraph('Text in second sub heading', PS('body')))
story.append(Paragraph('Last heading', h1))
doc = MyDocTemplate('mintoc.pdf')
doc.multiBuild(story)
9.9 SimpleIndex()
An index can be generated by using the SimpleIndex flowable. The following steps are needed to add an
index to your document:
Use the index tag in paragraphs to index terms:
story = []
Page 93
User Guide Chapter 9 Other Useful Flowables
...
Create an instance of SimpleIndex and add it to the story where you want it to appear:
The parameters which you can pass into the SimpleIndex constructor are explained in the reportlab reference.
Now, build the document by using the canvas maker returned by SimpleIndex.getCanvasMaker():
doc.build(story, canvasmaker=index.getCanvasMaker())
To build an index with multiple levels, pass a comma-separated list of items to the item attribute of an index
tag:
terma will respresent the top-most level and termc the most specific term. termd and termb will appear in the
same level inside terma.
If you need to index a term containing a comma, you will need to escape it by doubling it. To avoid the
ambiguity of three consecutive commas (an escaped comma followed by a list separator or a list separator
followed by an escaped comma?) introduce a space in the right position. Spaces at the beginning or end of
terms will be removed.
9.10 ListFlowable(),ListItem()
Use these classes to make ordered and unordered lists. Lists can be nested.
ListFlowable() will create an ordered list, which can contain any flowable. The class has a number of
parameters to change font, colour, size, style and position of list numbers, or of bullets in unordered lists. The
type of numbering can also be set to use lower or upper case letters ('A,B,C' etc.) or Roman numerals (capitals
or lowercase) using the bulletType property. To change the list to an unordered type, set bulletType='bullet'.
Items within a ListFlowable() list can be changed from their default appearance by wrapping them in a
ListItem() class and setting its properties.
The following will create an ordered list, and set the third item to an unordered sublist.
produces
i Item no.1
Page 94
User Guide Chapter 9 Other Useful Flowables
Page 95
User Guide Chapter 10 Writing your own Flowable Objects
To embed this or any other drawing in a Platypus flowable we must define a subclass of Flowable with at
least a wrap method and a draw method.
The wrap method must provide the size of the drawing -- it is used by the Platypus mainloop to decide
whether this element fits in the space remaining on the current frame. The draw method performs the
Page 96
User Guide Chapter 10 Writing your own Flowable Objects
drawing of the object after the Platypus mainloop has translated the (0,0) origin to an appropriate location
in an appropriate frame.
Below are some example uses of the HandAnnotation flowable.
The default.
One inch high and shifted to the left with blue and cyan.
class RotatedImage(Image):
def wrap(self,availWidth,availHeight):
h, w = Image.wrap(self,availHeight,availWidth)
return w, h
def draw(self):
self.canv.rotate(90)
Page 97
User Guide Chapter 10 Writing your own Flowable Objects
Image.draw(self)
I = RotatedImage('../images/replogo.gif')
I.hAlign = 'CENTER'
produces
Page 98
User Guide Chapter 11 Graphics
Chapter 11 Graphics
11.1 Introduction
ReportLab Graphics is one of the sub-packages to the ReportLab library. It started off as a stand-alone set of
programs, but is now a fully integrated part of the ReportLab toolkit that allows you to use its powerful
charting and graphics features to improve your PDF forms and reports.
Coordinate System
The Y-direction in our X-Y coordinate system points from the bottom up. This is consistent with PDF,
Postscript and mathematical notation. It also appears to be more natural for people, especially when working
with charts. Note that in other graphics models (such as SVG) the Y-coordinate points down. For the SVG
renderer this is actually no problem as it will take your drawings and flip things as needed, so your SVG
output looks just as expected.
The X-coordinate points, as usual, from left to right. So far there doesn't seem to be any model advocating
the opposite direction - at least not yet (with interesting exceptions, as it seems, for Arabs looking at time
series charts...).
Getting Started
Let's create a simple drawing containing the string "Hello World" and some special characters, displayed on
top of a coloured rectangle. After creating it we will save the drawing to a standalone PDF file.
Page 99
User Guide Chapter 11 Graphics
d = Drawing(400, 200)
d.add(Rect(50, 50, 300, 100, fillColor=colors.yellow))
d.add(String(150,100, 'Hello World', fontSize=18, fillColor=colors.red))
d.add(String(180,86, 'Special characters \
\xc2\xa2\xc2\xa9\xc2\xae\xc2\xa3\xce\xb1\xce\xb2',
fillColor=colors.red))
Hello World
Special characters
Each renderer is allowed to do whatever is appropriate for its format, and may have whatever API is needed.
If it refers to a file format, it usually has a drawToFile function, and that's all you need to know about the
renderer. Let's save the same drawing in Encapsulated Postscript format:
This will produce an EPS file with the identical drawing, which may be imported into publishing tools such
as Quark Express. If we wanted to generate the same drawing as a bitmap file for a website, say, all we need
to do is write code like this:
Many other bitmap formats, like GIF, JPG, TIFF, BMP and PPN are genuinely available, making it unlikely
you'll need to add external postprocessing steps to convert to the final format you need.
To produce an SVG file containing the identical drawing, which may be imported into graphical editing tools
such as Illustrator all we need to do is write code like this:
Attribute Verification
Python is very dynamic and lets us execute statements at run time that can easily be the source for
unexpected behaviour. One subtle 'error' is when assigning to an attribute that the framework doesn't know
about because the used attribute's name contains a typo. Python lets you get away with it (adding a new
attribute to an object, say), but the graphics framework will not detect this 'typo' without taking special
counter-measures.
Page 100
User Guide Chapter 11 Graphics
There are two verification techniques to avoid this situation. The default is for every object to check every
assignment at run time, such that you can only assign to 'legal' attributes. This is what happens by default. As
this imposes a small performance penalty, this behaviour can be turned off when you need it to be.
These statements would be caught by the compiler in a statically typed language, but Python lets you get
away with it. The first error could leave you staring at the picture trying to figure out why the colors were
wrong. The second error would probably become clear only later, when some back-end tries to draw the
rectangle. The third, though less likely, results in an invalid object that would not know how to draw itself.
>>> r = shapes.Rect(10,10,200,80)
>>> r.fullColor = colors.green
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "C:\code\users\andy\graphics\shapes.py", line 254, in __setattr__
validateSetattr(self,attr,value) #from reportlab.lib.attrmap
File "C:\code\users\andy\lib\attrmap.py", line 74, in validateSetattr
raise AttributeError, "Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__)
AttributeError: Illegal attribute 'fullColor' in class Rect
>>>
This imposes a performance penalty, so this behaviour can be turned off when you need it to be. To do this,
you should use the following lines of code before you first import reportlab.graphics.shapes:
Once you turn off shapeChecking, the classes are actually built without the verification hook; code
should get faster, then. Currently the penalty seems to be about 25% on batches of charts, so it is hardly worth
disabling. However, if we move the renderers to C in future (which is eminently possible), the remaining 75%
would shrink to almost nothing and the saving from verification would be significant.
Each object, including the drawing itself, has a verify() method. This either succeeds, or raises an
exception. If you turn off automatic verification, then you should explicitly call verify() in testing when
developing the code, or perhaps once in a batch process.
Property Editing
A cornerstone of the reportlab/graphics which we will cover below is that you can automatically document
widgets. This means getting hold of all of their editable properties, including those of their subcomponents.
Another goal is to be able to create GUIs and config files for drawings. A generic GUI can be built to show
all editable properties of a drawing, and let you modify them and see the results. The Visual Basic or Delphi
development environment are good examples of this kind of thing. In a batch charting application, a file
could list all the properties of all the components in a chart, and be merged with a database query to make a
batch of charts.
To support these applications we have two interfaces, getProperties and setProperties, as well as
a convenience method dumpProperties. The first returns a dictionary of the editable properties of an
object; the second sets them en masse. If an object has publicly exposed 'children' then one can recursively set
and get their properties too. This will make much more sense when we look at Widgets later on, but we need
to put the support into the base of the framework.
>>> r = shapes.Rect(0,0,200,100)
>>> import pprint
>>> pprint.pprint(r.getProperties())
{'fillColor': Color(0.00,0.00,0.00),
'height': 100,
'rx': 0,
'ry': 0,
Page 101
User Guide Chapter 11 Graphics
'strokeColor': Color(0.00,0.00,0.00),
'strokeDashArray': None,
'strokeLineCap': 0,
'strokeLineJoin': 0,
'strokeMiterLimit': 0,
'strokeWidth': 1,
'width': 200,
'x': 0,
'y': 0}
>>> r.setProperties({'x':20, 'y':30, 'strokeColor': colors.red})
>>> r.dumpProperties()
fillColor = Color(0.00,0.00,0.00)
height = 100
rx = 0
ry = 0
strokeColor = Color(1.00,0.00,0.00)
strokeDashArray = None
strokeLineCap = 0
strokeLineJoin = 0
strokeMiterLimit = 0
strokeWidth = 1
width = 200
x = 20
y = 30
>>>
Note: pprint is the standard Python library module that allows you to 'pretty print' output over multiple
lines rather than having one very long line.
These three methods don't seem to do much here, but as we will see they make our widgets framework much
more powerful when dealing with non-primitive objects.
Naming Children
You can add objects to the Drawing and Group objects. These normally go into a list of contents.
However, you may also give objects a name when adding them. This allows you to refer to and possibly
change any element of a drawing after constructing it.
Note that you can use the same shape instance in several contexts in a drawing; if you choose to use the same
Circle object in many locations (e.g. a scatter plot) and use different names to access it, it will still be a
shared object and the changes will be global.
This provides one paradigm for creating and modifying interactive drawings.
11.3 Charts
The motivation for much of this is to create a flexible chart package. This section presents a treatment of the
ideas behind our charting model, what the design goals are and what components of the chart package
already exist.
Design Goals
Here are some of the design goals:
Make simple top-level use really simple
It should be possible to create a simple chart with minimum lines of code, yet have it 'do the right
things' with sensible automatic settings. The pie chart snippets above do this. If a real chart has
many subcomponents, you still should not need to interact with them unless you want to customize
what they do.
Allow precise positioning
Page 102
User Guide Chapter 11 Graphics
An absolute requirement in publishing and graphic design is to control the placing and style of every
element. We will try to have properties that specify things in fixed sizes and proportions of the
drawing, rather than having automatic resizing. Thus, the 'inner plot rectangle' will not magically
change when you make the font size of the y labels bigger, even if this means your labels can spill
out of the left edge of the chart rectangle. It is your job to preview the chart and choose sizes and
spaces which will work.
Some things do need to be automatic. For example, if you want to fit N bars into a 200 point space
and don't know N in advance, we specify bar separation as a percentage of the width of a bar rather
than a point size, and let the chart work it out. This is still deterministic and controllable.
Control child elements individually or as a group
We use smart collection classes that let you customize a group of things, or just one of them. For
example you can do this in our experimental pie chart:
d = Drawing(400,200)
pc = Pie()
pc.x = 150
pc.y = 50
pc.data = [10,20,30,40,50,60]
pc.labels = ['a','b','c','d','e','f']
pc.slices.strokeWidth=0.5
pc.slices[3].popout = 20
pc.slices[3].strokeWidth = 2
pc.slices[3].strokeDashArray = [2,2]
pc.slices[3].labelRadius = 1.75
pc.slices[3].fontColor = colors.red
d.add(pc, '')
pc.slices[3] actually lazily creates a little object which holds information about the slice in question;
this will be used to format a fourth slice at draw-time if there is one.
Only expose things you should change
It would be wrong from a statistical viewpoint to let you directly adjust the angle of one of the pie
slices in the above example, since that is determined by the data. So not everything will be exposed
through the public properties. There may be 'back doors' to let you violate this when you really need
to, or methods to provide advanced functionality, but in general properties will be orthogonal.
Composition and component based
Charts are built out of reusable child widgets. A Legend is an easy-to-grasp example. If you need a
specialized type of legend (e.g. circular colour swatches), you should subclass the standard Legend
widget. Then you could either do something like...
c = MyChartWithLegend()
c.legend = MyNewLegendClass() # just change it
c.legend.swatchRadius = 5 # set a property only relevant to the new one
c.data = [10,20,30] # and then configure as usual...
...or create/modify your own chart or drawing class which creates one of these by default. This is
also very relevant for time series charts, where there can be many styles of x axis.
Top level chart classes will create a number of such components, and then either call methods or set
private properties to tell them their height and position - all the stuff which should be done for you
and which you cannot customise. We are working on modelling what the components should be and
will publish their APIs here as a consensus emerges.
Multiples
A corollary of the component approach is that you can create diagrams with multiple charts, or
custom data graphics. Our favourite example of what we are aiming for is the weather report in our
gallery contributed by a user; we'd like to make it easy to create such drawings, hook the building
blocks up to their legends, and feed that data in a consistent way.
(If you want to see the image, it is available on our website here)
Page 103
User Guide Chapter 11 Graphics
Overview
A chart or plot is an object which is placed on a drawing; it is not itself a drawing. You can thus control
where it goes, put several on the same drawing, or add annotations.
Charts have two axes; axes may be Value or Category axes. Axes in turn have a Labels property which lets
you configure all text labels or each one individually. Most of the configuration details which vary from chart
to chart relate to axis properties, or axis labels.
Objects expose properties through the interfaces discussed in the previous section; these are all optional and
are there to let the end user configure the appearance. Things which must be set for a chart to work, and
essential communication between a chart and its components, are handled through methods.
You can subclass any chart component and use your replacement instead of the original provided you
implement the essential methods and properties.
11.4 Labels
A label is a string of text attached to some chart element. They are used on axes, for titles or alongside axes,
or attached to individual data points. Labels may contain newline characters, but only one font.
The text and 'origin' of a label are typically set by its parent object. They are accessed by methods rather than
properties. Thus, the X axis decides the 'reference point' for each tickmark label and the numeric or date text
for each label. However, the end user can set properties of the label (or collection of labels) directly to affect
its position relative to this origin and all of its formatting.
d = Drawing(200, 100)
lab = Label()
lab.setOrigin(100,90)
lab.boxAnchor = 'ne'
lab.angle = 45
lab.dx = 0
lab.dy = -20
lab.boxStrokeColor = colors.green
lab.setText('Some
Multi-Line
Label')
d.add(lab)
e
l in
M ome
be i-L
La ult
S
In the drawing above, the label is defined relative to the green blob. The text box should have its north-east
corner ten points down from the origin, and be rotated by 45 degrees about that corner.
At present labels have the following properties, which we believe are sufficient for all charts we have seen to
date:
Property Meaning
Page 104
User Guide Chapter 11 Graphics
11.5 Axes
We identify two basic kinds of axes - Value and Category ones. Both come in horizontal and vertical flavors.
Both can be subclassed to make very specific kinds of axis. For example, if you have complex rules for which
dates to display in a time series application, or want irregular scaling, you override the axis and make a new
one.
Axes are responsible for determining the mapping from data to image coordinates; transforming points on
request from the chart; drawing themselves and their tickmarks, gridlines and axis labels.
This drawing shows two axes, one of each kind, which have been created directly without reference to any
chart:
Page 105
User Guide Chapter 11 Graphics
40
30
20
xAxis = XCategoryAxis()
xAxis.setPosition(75, 75, 300)
xAxis.configure(data)
xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
xAxis.labels.boxAnchor = 'n'
xAxis.labels[3].dy = -15
xAxis.labels[3].angle = 30
xAxis.labels[3].fontName = 'Times-Bold'
yAxis = YValueAxis()
yAxis.setPosition(50, 50, 125)
yAxis.configure(data)
drawing.add(xAxis)
drawing.add(yAxis)
Remember that, usually, you won't have to create axes directly; when using a standard chart, it comes with
ready-made axes. The methods are what the chart uses to configure it and take care of the geometry.
However, we will talk through them in detail below. The orthogonally dual axes to those we describe have
essentially the same properties, except for those refering to ticks.
XCategoryAxis class
A Category Axis doesn't really have a scale; it just divides itself into equal-sized buckets. It is simpler than a
value axis. The chart (or programmer) sets its location with the method setPosition(x, y, length).
The next stage is to show it the data so that it can configure itself. This is easy for a category axis - it just
counts the number of data points in one of the data series. The reversed attribute (if 1) indicates that the
categories should be reversed. When the drawing is drawn, the axis can provide some help to the chart with
its scale() method, which tells the chart where a given category begins and ends on the page. We have not
yet seen any need to let people override the widths or positions of categories.
An XCategoryAxis has the following editable properties:
Property Meaning
Page 106
User Guide Chapter 11 Graphics
YValueAxis
The left axis in the diagram is a YValueAxis. A Value Axis differs from a Category Axis in that each point
along its length corresponds to a y value in chart space. It is the job of the axis to configure itself, and to
convert Y values from chart space to points on demand to assist the parent chart in plotting.
setPosition(x, y, length) and configure(data) work exactly as for a category axis. If you
have not fully specified the maximum, minimum and tick interval, then configure() results in the axis
choosing suitable values. Once configured, the value axis can convert y data values to drawing space with the
scale() method. Thus:
By default, the highest data point is aligned with the top of the axis, the lowest with the bottom of the axis,
and the axis choose 'nice round numbers' for its tickmark points. You may override these settings with the
properties below.
Property Meaning
Should the axis be drawn at all? Sometimes you don't want
visible to display one or both axes, but they still need to be there as
they manage the scaling of points.
strokeColor Color of the axis
Whether to draw axis with a dash and, if so, what kind.
strokeDashArray
Defaults to None
Page 107
User Guide Chapter 11 Graphics
xAxis = XValueAxis()
xAxis.setPosition(75, 50, 300)
xAxis.valueSteps = [10, 15, 20, 30, 35, 40]
xAxis.configure(data)
xAxis.labels.boxAnchor = 'n'
drawing.add(xAxis)
Page 108
User Guide Chapter 11 Graphics
10 15 20 30 35 40
In addition to these properties, all axes classes have three properties describing how to join two of them to
each other. Again, this is interesting only if you define your own charts or want to modify the appearance of
an existing chart using such axes. These properties are listed here only very briefly for now, but you can find
a host of sample functions in the module reportlab/graphics/axes.py which you can examine...
One axis is joined to another, by calling the method joinToAxis(otherAxis, mode, pos) on the
first axis, with mode and pos being the properties described by joinAxisMode and joinAxisPos,
respectively. 'points' means to use an absolute value, and 'value' to use a relative value (both
indicated by the the joinAxisPos property) along the axis.
Property Meaning
joinAxis Join both axes if true.
joinAxisMode Mode used for connecting axis ('bottom', 'top', 'left', 'right', 'value', 'points', None).
joinAxisPos Position at which to join with other axis.
50
40
30
20
10
0
-99 b-9
9
r-9
9
r-9
9
y-9
9 -99 -99
g-9
9
Jan Fe Ma Ap Ma Jun Jul Au
Page 109
User Guide Chapter 11 Graphics
data = [
(13, 5, 20, 22, 37, 45, 19, 4),
(14, 6, 21, 23, 38, 46, 20, 5)
]
bc = VerticalBarChart()
bc.x = 50
bc.y = 50
bc.height = 125
bc.width = 300
bc.data = data
bc.strokeColor = colors.black
bc.valueAxis.valueMin = 0
bc.valueAxis.valueMax = 50
bc.valueAxis.valueStep = 10
bc.categoryAxis.labels.boxAnchor = 'ne'
bc.categoryAxis.labels.dx = 8
bc.categoryAxis.labels.dy = -2
bc.categoryAxis.labels.angle = 30
bc.categoryAxis.categoryNames = ['Jan-99','Feb-99','Mar-99',
'Apr-99','May-99','Jun-99','Jul-99','Aug-99']
drawing.add(bc)
Most of this code is concerned with setting up the axes and labels, which we have already covered. Here are
the top-level properties of the VerticalBarChart class:
Property Meaning
This should be a "list of lists of numbers" or "list of
data tuples of numbers". If you have just one series, write it as
data = [(10,20,30,42),]
These define the inner 'plot rectangle'. We
highlighted this with a yellow border above. Note that it is
your job to place the chart on the drawing in a way which leaves
x, y, width, height
room for all the axis labels and tickmarks. We specify this 'inner
rectangle' because it makes it very easy to lay out multiple charts
in a consistent manner.
Defaults to None. This will draw a border around the
strokeColor plot rectangle, which may be useful in debugging. Axes will
overwrite this.
Defaults to None. This will fill the plot rectangle with
fillColor a solid color. (Note that we could implement dashArray etc.
as for any other solid shape)
Defaults to 0. If 1, the three properties below are
absolute values in points (which means you can make a chart
useAbsolute where the bars stick out from the plot rectangle); if 0,
they are relative quantities and indicate the proportional
widths of the elements involved.
barWidth As it says. Defaults to 10.
Defaults to 5. This is the space between each group of
bars. If you have only one series, use groupSpacing and not
groupSpacing
barSpacing to split them up. Half of the groupSpacing is used
before the first bar in the chart, and another half at the end.
Page 110
User Guide Chapter 11 Graphics
bc.groupSpacing = 10
bc.barSpacing = 2.5
And, in fact, this is exactly what we can see after adding these lines to the code above. Notice how the width
of the individual bars has changed as well. This is because the space added between the bars has to be 'taken'
from somewhere as the total chart width stays unchanged.
50
40
30
20
10
0
-99 b-9
9
r-9
9
r-9
9
y-9
9 -99 -99
g-9
9
Jan Fe Ma Ap Ma Jun Jul Au
Bars labels are automatically displayed for negative values below the lower end of the bar for positive values
above the upper end of the other ones.
Stacked bars are also supported for vertical bar graphs. You enable this layout for your chart by setting the
style attribute to 'stacked' on the categoryAxis.
Page 111
User Guide Chapter 11 Graphics
bc.categoryAxis.style = 'stacked'
Here is an example of the previous chart values arranged in the stacked style.
100
80
60
40
20
0
-99 b-9
9
r-9
9
r-9
9
y-9
9 -99 -99
g-9
9
Jan Fe Ma Ap Ma Jun Jul Au
data = [
(13, 5, 20, 22, 37, 45, 19, 4),
(5, 20, 46, 38, 23, 21, 6, 14)
]
lc = HorizontalLineChart()
lc.x = 50
lc.y = 50
lc.height = 125
lc.width = 300
lc.data = data
lc.joinedLines = 1
catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
lc.categoryAxis.categoryNames = catNames
lc.categoryAxis.labels.boxAnchor = 'n'
lc.valueAxis.valueMin = 0
lc.valueAxis.valueMax = 60
lc.valueAxis.valueStep = 15
lc.lines[0].strokeWidth = 2
lc.lines[1].strokeWidth = 1.5
drawing.add(lc)
Page 112
User Guide Chapter 11 Graphics
60
45
30
15
0
Jan Feb Mar Apr May Jun Jul Aug
Property Meaning
data Data to be plotted, list of (lists of) numbers.
Bounding box of the line chart.
x, y, width, height
Note that x and y do NOT specify the centre but the bottom left corner
valueAxis The value axis, which may be formatted as described previously.
categoryAxis The category axis, which may be formatted as described previously.
Defaults to None. This will draw a border around the plot rectangle,
strokeColor
which may be useful in debugging. Axes will overwrite this.
fillColor Defaults to None. This will fill the plot rectangle with a solid color.
lines.strokeColor Color of the line.
lines.strokeWidth Width of the line.
A collection of labels used to format all line labels. Since
this is a two-dimensional array, you may explicitly format the
lineLabels
third label of the second line using this syntax:
chart.lineLabels[(1,2)].fontSize = 12
Defaults to None. As with the YValueAxis, if you supply
a function or format string then labels will be drawn next
lineLabelFormat
to each line showing the numeric value. You can also set it
to 'values' to display the values explicity defined in lineLabelArray.
Explicit array of line label values, must match size of data if present.
lineLabelArray These labels values will be displayed only if the property
lineLabelFormat above is set to 'values'.
Page 113
User Guide Chapter 11 Graphics
data = [
((1,1), (2,2), (2.5,1), (3,3), (4,5)),
((1,2), (2,3), (2.5,2), (3.5,5), (4,6))
]
lp = LinePlot()
lp.x = 50
lp.y = 50
lp.height = 125
lp.width = 300
lp.data = data
lp.joinedLines = 1
lp.lines[0].symbol = makeMarker('FilledCircle')
lp.lines[1].symbol = makeMarker('Circle')
lp.lineLabelFormat = '%2.0f'
lp.strokeColor = colors.black
lp.xValueAxis.valueMin = 0
lp.xValueAxis.valueMax = 5
lp.xValueAxis.valueSteps = [1, 2, 2.5, 3, 4, 5]
lp.xValueAxis.labelTextFormat = '%2.1f'
lp.yValueAxis.valueMin = 0
lp.yValueAxis.valueMax = 7
lp.yValueAxis.valueSteps = [1, 2, 3, 5, 6]
drawing.add(lp)
6
6
5 5
5
3 3
3
2 2 2
2
1 1
1
Property Meaning
data Data to be plotted, list of (lists of) numbers.
Bounding box of the line chart.
x, y, width, height
Note that x and y do NOT specify the centre but the bottom left corner
xValueAxis The vertical value axis, which may be formatted as described previously.
yValueAxis The horizontal value axis, which may be formatted as described previously.
Defaults to None. This will draw a border around the plot rectangle,
strokeColor
which may be useful in debugging. Axes will overwrite this.
strokeWidth Defaults to None. Width of the border around the plot rectangle.
fillColor Defaults to None. This will fill the plot rectangle with a solid color.
lines.strokeColor Color of the line.
Page 114
User Guide Chapter 11 Graphics
pc = Pie()
pc.x = 65
pc.y = 15
pc.width = 70
pc.height = 70
pc.data = [10,20,30,40,50,60]
pc.labels = ['a','b','c','d','e','f']
pc.slices.strokeWidth=0.5
pc.slices[3].popout = 10
pc.slices[3].strokeWidth = 2
pc.slices[3].strokeDashArray = [2,2]
pc.slices[3].labelRadius = 1.75
pc.slices[3].fontColor = colors.red
d.add(pc)
a
b
f
e d
Page 115
User Guide Chapter 11 Graphics
Properties are covered below. The pie has a 'slices' collection and we document wedge properties in the same
table.
Property Meaning
data A list or tuple of numbers
Bounding box of the pie.
Note that x and y do NOT specify the centre but the bottom left
x, y, width, height
corner, and that width and height do not have to be equal;
pies may be elliptical and slices will be drawn correctly.
None, or a list of strings.
Make it None if you don't want labels around the edge of the pie.
labels Since it is impossible to know the size of slices, we generally
discourage placing labels in or around pies; it is much better
to put them in a legend alongside.
Where is the start angle of the first pie slice?
startAngle
The default is '90' which is twelve o'clock.
Which direction do slices progress in?
direction
The default is 'clockwise'.
This creates a chart with the labels in two columns,
sideLabels
one on either side.
This is a fraction of the width of the pie that defines the horizontal
sideLabelsOffset
distance between the pie and the columns of labels.
Default is 1. Set to 0 to enable the use of customizable labels
simpleLabels
and of properties prefixed by label_ in the collection slices.
Collection of slices.
slices
This lets you customise each wedge, or individual ones. See below
slices.strokeWidth Border width for wedge
slices.strokeColor Border color
slices.strokeDashArray Solid or dashed line configuration
How far out should the slice(s) stick from the centre of the pie?
slices.popout
Default is zero.
slices.fontName Name of the label font
slices.fontSize Size of the label font
slices.fontColor Color of the label text
This controls the anchor point for a text label.
It is a fraction of the radius; 0.7 will place the text inside the
slices.labelRadius
pie, 1.2 will place it slightly outside. (note that if we add labels,
we will keep this to specify their anchor point)
Customizing Labels
Each slide label can be customized individually by changing the properties prefixed by label_ in the
collection slices. For example pc.slices[2].label_angle = 10 changes the angle of the third
label.
Before being able to use these customization properties, you need to disable simple labels with:
pc.simplesLabels = 0
Page 116
User Guide Chapter 11 Graphics
Property Meaning
label_dx X Offset of the label
label_dy Y Offset of the label
Angle of the label, default (0) is horizontal, 90 is vertical,
label_angle
180 is upside down
label_boxAnchor Anchoring point of the label
label_boxStrokeColor Border color for the label box
label_boxStrokeWidth Border width for the label box
label_boxFillColor Filling color of the label box
label_strokeColor Border color for the label text
label_strokeWidth Border width for the label text
label_text Text of the label
label_width Width of the label
label_maxWidth Maximum width the label can grow to
label_height Height of the label
label_textAnchor Maximum height the label can grow to
label_visible True if the label is to be drawn
label_topPadding Padding at top of box
label_leftPadding Padding at left of box
label_rightPadding Padding at right of box
label_bottomPadding Padding at bottom of box
label_simple_pointer Set to 1 for simple pointers
label_pointer_strokeColor Color of indicator line
label_pointer_strokeWidth Width of indicator line
Side Labels
If the sideLabels attribute is set to true, then the labels of the slices are placed in two columns, one on either
side of the pie and the start angle of the pie will be set automatically. The anchor of the right hand column is
set to 'start' and the anchor of the left hand column is set to 'end'. The distance from the edge of the pie from
the edge of either column is decided by the sideLabelsOffset attribute, which is a fraction of the width of the
pie. If xradius is changed, the pie can overlap the labels, and so we advise leaving xradius as None. There is
an example below.
Page 117
User Guide Chapter 11 Graphics
example2
example3
example4
example1
example5
example6
If you have sideLabels set to True, then some of the attributes become redundant, such as pointerLabelMode.
Also sideLabelsOffset only changes the piechart if sideLabels is set to true.
Some issues
The pointers can cross if there are too many slices.
example9
example10
example11 example12
example13
example8 example14
example7 example15
example6 example16
example5 example17
example4 example18
example3 example19
example2 example20
example1 example21
example28 example22
example27
example26 example25
example24
example23
Also the labels can overlap despite checkLabelOverlap if they correspond to slices that are not adjacent.
Page 118
User Guide Chapter 11 Graphics
example7
example1
example6
example2 example8
example3 example9
example4 example10
example5 example11
example12
example13
example16
example15 example14
11.10 Legends
Various preliminary legend classes can be found but need a cleanup to be consistent with the rest of the
charting model. Legends are the natural place to specify the colors and line styles of charts; we propose that
each chart is created with a legend attribute which is invisible. One would then do the following to specify
colors:
One could also define a group of charts sharing the same legend:
myLegend = Legend()
myLegend.defaultColor = [red, green.....] #yuck!
myLegend.columns = 2
# etc.
chart1.legend = myLegend
chart2.legend = myLegend
chart3.legend = myLegend
Does this work? Is it an acceptable complication over specifying chart colors directly?
Remaining Issues
There are several issues that are almost solved, but for which is is a bit too early to start making them really
public. Nevertheless, here is a list of things that are under way:
Color specification - right now the chart has an undocumented property defaultColors,
which provides a list of colors to cycle through, such that each data series gets its own color.
Right now, if you introduce a legend, you need to make sure it shares the same list of colors.
Most likely, this will be replaced with a scheme to specify a kind of legend containing attributes
with different values for each data series. This legend can then also be shared by several charts,
but need not be visible itself.
Additional chart types - when the current design will have become more stable, we expect to
add variants of bar charts to deal with percentile bars as well as the side-by-side variant seen
here.
Outlook
It will take some time to deal with the full range of chart types. We expect to finalize bars and pies first and
to produce trial implementations of more general plots, thereafter.
Page 119
User Guide Chapter 11 Graphics
X-Y Plots
Most other plots involve two value axes and directly plotting x-y data in some form. The series can be plotted
as lines, marker symbols, both, or custom graphics such as open-high-low-close graphics. All share the
concepts of scaling and axis/title formatting. At a certain point, a routine will loop over the data series and 'do
something' with the data points at given x-y locations. Given a basic line plot, it should be very easy to derive
a custom chart type just by overriding a single method - say, drawSeries().
Combination plots
Combining multiple plot types is really easy. You can just draw several charts (bar, line or whatever) in the
same rectangle, suppressing axes as needed. So a chart could correlate a line with Scottish typhoid cases over
a 15 year period on the left axis with a set of bars showing inflation rates on the right axis. If anyone can
remind us where this example came from we'll attribute it, and happily show the well-known graph as an
example.
Interactive editors
One principle of the Graphics package is to make all 'interesting' properties of its graphic components
accessible and changeable by setting apropriate values of corresponding public attributes. This makes it very
tempting to build a tool like a GUI editor that that helps you with doing that interactively.
ReportLab has built such a tool using the Tkinter toolkit that loads pure Python code describing a drawing
and records your property editing operations. This "change history" is then used to create code for a subclass
of that chart, say, that can be saved and used instantly just like any other chart or as a new starting point for
another interactive editing session.
This is still work in progress, though, and the conditions for releasing this need to be further elaborated.
Misc.
This has not been an exhaustive look at all the chart classes. Those classes are constantly being worked on. To
see exactly what is in the current distribution, use the graphdocpy.py utility. By default, it will run on
reportlab/graphics, and produce a full report. (If you want to run it on other modules or packages,
graphdocpy.py -h prints a help message that will tell you how.)
This is the tool that was mentioned in the section on 'Documenting Widgets'.
11.11 Shapes
This section describes the concept of shapes and their importance as building blocks for all output generated
by the graphics library. Some properties of existing shapes and their relationship to diagrams are presented
and the notion of having different renderers for different output formats is briefly introduced.
Available Shapes
Drawings are made up of Shapes. Absolutely anything can be built up by combining the same set of primitive
shapes. The module shapes.py supplies a number of primitive shapes and constructs which can be added
to a drawing. They are:
Rect
Circle
Ellipse
Wedge (a pie slice)
Polygon
Line
Page 120
User Guide Chapter 11 Graphics
PolyLine
String
Group
Path (not implemented yet, but will be added in the future)
The following drawing, taken from our test suite, shows most of the basic shapes (except for groups). Those
with a filled green surface are also called solid shapes (these are Rect, Circle, Ellipse, Wedge and
Polygon).
Basic Shapes
Shape Properties
Shapes have two kinds of properties - some to define their geometry and some to define their style. Let's
create a red rectangle with 3-point thick green borders:
Page 121
User Guide Chapter 11 Graphics
All shapes have a number of properties which can be set. At an interactive prompt, we can use their
dumpProperties() method to list these. Here's what you can use to configure a Rect:
>>> r.dumpProperties()
fillColor = Color(1.00,0.00,0.00)
height = 100
rx = 0
ry = 0
strokeColor = Color(0.00,0.50,0.00)
strokeDashArray = None
strokeLineCap = 0
strokeLineJoin = 0
strokeMiterLimit = 0
strokeWidth = 3
width = 200
x = 5
y = 5
>>>
Shapes generally have style properties and geometry properties. x, y, width and height are part of the
geometry and must be provided when creating the rectangle, since it does not make much sense without those
properties. The others are optional and come with sensible defaults.
You may set other properties on subsequent lines, or by passing them as optional arguments to the
constructor. We could also have created our rectangle this way:
Let's run through the style properties. fillColor is obvious. stroke is publishing terminology for the
edge of a shape; the stroke has a color, width, possibly a dash pattern, and some (rarely used) features for
what happens when a line turns a corner. rx and ry are optional geometric properties and are used to define
the corner radius for a rounded rectangle.
All the other solid shapes share the same style properties.
Lines
We provide single straight lines, PolyLines and curves. Lines have all the stroke* properties, but no
fillColor. Here are a few Line and PolyLine examples and the corresponding graphics output:
Line(50,50, 300,100,
strokeColor=colors.blue, strokeWidth=5)
Line(50,100, 300,50,
strokeColor=colors.red,
strokeWidth=10,
strokeDashArray=[10, 20])
PolyLine([120,110, 130,150, 140,110, 150,150, 160,110,
170,150, 180,110, 190,150, 200,110],
strokeWidth=2,
strokeColor=colors.purple)
Page 122
User Guide Chapter 11 Graphics
Strings
The ReportLab Graphics package is not designed for fancy text layout, but it can place strings at desired
locations and with left/right/center alignment. Let's specify a String object and look at its properties:
Strings have a textAnchor property, which may have one of the values 'start', 'middle', 'end'. If this is set to
'start', x and y relate to the start of the string, and so on. This provides an easy way to align text.
Strings use a common font standard: the Type 1 Postscript fonts present in Acrobat Reader. We can thus use
the basic 14 fonts in ReportLab and get accurate metrics for them. We have recently also added support for
extra Type 1 fonts and the renderers all know how to render Type 1 fonts.
Here is a more fancy example using the code snippet below. Please consult the ReportLab User Guide to see
how non-standard like 'DarkGardenMK' fonts are being registered!
d = Drawing(400, 200)
for size in range(12, 36, 4):
d.add(String(10+size*2, 10+size*2, 'Hello World',
fontName='Times-Roman',
fontSize=size))
Page 123
User Guide Chapter 11 Graphics
Hello World
Hello World
Hello
Hello World
World
Hello World
Hello World
Hello World
Hello World
Paths
Postscript paths are a widely understood concept in graphics. They are not implemented in
reportlab/graphics as yet, but they will be, soon.
Groups
Finally, we have Group objects. A group has a list of contents, which are other nodes. It can also apply a
transformation - its contents can be rotated, scaled or shifted. If you know the math, you can set the transform
directly. Otherwise it provides methods to rotate, scale and so on. Here we make a group which is rotated and
translated:
Groups provide a tool for reuse. You can make a bunch of shapes to represent some component - say, a
coordinate system - and put them in one group called "Axis". You can then put that group into other groups,
each with a different translation and rotation, and you get a bunch of axis. It is still the same group, being
drawn in different places.
Let's do this with some only slightly more code:
d = Drawing(400, 200)
Axis = Group(
Line(0,0,100,0), # x axis
Line(0,0,0,50), # y axis
Line(0,10,10,10), # ticks on y axis
Line(0,20,10,20),
Line(0,30,10,30),
Line(0,40,10,40),
Line(10,0,10,10), # ticks on x axis
Line(20,0,20,10),
Line(30,0,30,10),
Line(40,0,40,10),
Line(50,0,50,10),
Line(60,0,60,10),
Line(70,0,70,10),
Line(80,0,80,10),
Line(90,0,90,10),
String(20, 35, 'Axes', fill=colors.black)
)
Page 124
User Guide Chapter 11 Graphics
firstAxisGroup = Group(Axis)
firstAxisGroup.translate(10,10)
d.add(firstAxisGroup)
secondAxisGroup = Group(Axis)
secondAxisGroup.translate(150,10)
secondAxisGroup.rotate(15)
d.add(secondAxisGroup)
thirdAxisGroup = Group(Axis,
transform=mmult(translate(300,10),
rotate(30)))
d.add(thirdAxisGroup)
es
Axes Ax
Axes
11.12 Widgets
We now describe widgets and how they relate to shapes. Using many examples it is shown how widgets
make reusable graphics components.
Page 125
User Guide Chapter 11 Graphics
Widgets run contrary to the idea that a drawing is just a bundle of shapes; surely they have their own code?
The way they work is that a widget can convert itself to a group of primitive shapes. If some of its
components are themselves widgets, they will get converted too. This happens automatically during
rendering; the renderer will not see your chart widget, but just a collection of rectangles, lines and strings.
You can also explicitly 'flatten out' a drawing, causing all widgets to be converted to primitives.
Using a Widget
Let's imagine a simple new widget. We will use a widget to draw a face, then show how it was implemented.
Let's see what properties it has available, using the setProperties() method we have seen earlier:
>>> f.dumpProperties()
eyeColor = Color(0.00,0.00,1.00)
mood = sad
size = 80
skinColor = Color(1.00,1.00,0.00)
x = 10
y = 10
>>>
One thing which seems strange about the above code is that we did not set the size or position when we made
the face. This is a necessary trade-off to allow a uniform interface for constructing widgets and documenting
them - they cannot require arguments in their __init__() method. Instead, they are generally designed to
fit in a 200 x 100 window, and you move or resize them by setting properties such as x, y, width and so on
after creation.
In addition, a widget always provides a demo() method. Simple ones like this always do something sensible
before setting properties, but more complex ones like a chart would not have any data to plot. The
documentation tool calls demo() so that your fancy new chart class can create a drawing showing what it
can do.
Here are a handful of simple widgets available in the module signsandsymbols.py:
Page 126
User Guide Chapter 11 Graphics
And this is the code needed to generate them as seen in the drawing above:
d = Drawing(230, 230)
ne = signsandsymbols.NoEntry()
ds = signsandsymbols.DangerSign()
fd = signsandsymbols.FloppyDisk()
ns = signsandsymbols.NoSmoking()
d.add(ne)
d.add(ds)
d.add(fd)
d.add(ns)
Compound Widgets
Let's imagine a compound widget which draws two faces side by side. This is easy to build when you have
the Face widget.
>>> tf = widgetbase.TwoFaces()
>>> tf.faceOne.mood
'happy'
>>> tf.faceTwo.mood
'sad'
>>> tf.dumpProperties()
faceOne.eyeColor = Color(0.00,0.00,1.00)
faceOne.mood = happy
faceOne.size = 80
faceOne.skinColor = None
faceOne.x = 10
faceOne.y = 10
faceTwo.eyeColor = Color(0.00,0.00,1.00)
faceTwo.mood = sad
faceTwo.size = 80
faceTwo.skinColor = None
Page 127
User Guide Chapter 11 Graphics
faceTwo.x = 100
faceTwo.y = 10
>>>
The attributes 'faceOne' and 'faceTwo' are deliberately exposed so you can get at them directly. There could
also be top-level attributes, but there aren't in this case.
Verifying Widgets
The widget designer decides the policy on verification, but by default they work like shapes - checking every
assignment - if the designer has provided the checking information.
Implementing Widgets
We tried to make it as easy to implement widgets as possible. Here's the code for a Face widget which does
not do any type checking:
class Face(Widget):
"""This draws a face with two eyes, mouth and nose."""
def __init__(self):
self.x = 10
self.y = 10
self.size = 80
self.skinColor = None
self.eyeColor = colors.blue
self.mood = 'happy'
def draw(self):
s = self.size # abbreviate as we will use this a lot
g = shapes.Group()
g.transform = [1,0,0,1,self.x, self.y]
# background
g.add(shapes.Circle(s * 0.5, s * 0.5, s * 0.5,
fillColor=self.skinColor))
# CODE OMITTED TO MAKE MORE SHAPES
return g
We left out all the code to draw the shapes in this document, but you can find it in the distribution in
widgetbase.py.
By default, any attribute without a leading underscore is returned by setProperties. This is a deliberate policy
to encourage consistent coding conventions.
Once your widget works, you probably want to add support for verification. This involves adding a dictionary
to the class called _verifyMap, which map from attribute names to 'checking functions'. The
widgetbase.py module defines a bunch of checking functions with names like isNumber,
isListOfShapes and so on. You can also simply use None, which means that the attribute must be
present but can have any type. And you can and should write your own checking functions. We want to
restrict the "mood" custom attribute to the values "happy", "sad" or "ok". So we do this:
class Face(Widget):
"""This draws a face with two eyes. It exposes a
couple of properties to configure itself and hides
all other details"""
def checkMood(moodName):
return (moodName in ('happy','sad','ok'))
_verifyMap = {
'x': shapes.isNumber,
'y': shapes.isNumber,
'size': shapes.isNumber,
'skinColor':shapes.isColorOrNone,
'eyeColor': shapes.isColorOrNone,
'mood': checkMood
}
This checking will be performed on every attribute assignment; or, if config.shapeChecking is off,
whenever you call myFace.verify().
Page 128
User Guide Chapter 11 Graphics
Documenting Widgets
We are working on a generic tool to document any Python package or module; this is already checked into
ReportLab and will be used to generate a reference for the ReportLab package. When it encounters widgets,
it adds extra sections to the manual including:
the doc string for your widget class
the code snippet from your demo() method, so people can see how to use it
the drawing produced by the demo() method
the property dump for the widget in the drawing.
This tool will mean that we can have guaranteed up-to-date documentation on our widgets and charts, both
on the web site and in print; and that you can do the same for your own widgets, too!
The last line is problematic as we have only created four slices - in fact we might not have created them yet.
Does pc.slices[7] raise an error? Is it a prescription for what should happen if a seventh wedge is
defined, used to override the default settings? We dump this problem squarely on the widget author for now,
and recommend that you get a simple one working before exposing 'child objects' whose existence depends
on other properties' values :-)
We also discussed rules by which parent widgets could pass properties to their children. There seems to be a
general desire for a global way to say that 'all slices get their lineWidth from the lineWidth of their parent'
without a lot of repetitive coding. We do not have a universal solution, so again leave that to widget authors.
We hope people will experiment with push-down, pull-down and pattern-matching approaches and come up
with something nice. In the meantime, we certainly can write monolithic chart widgets which work like the
ones in, say, Visual Basic and Delphi.
For now have a look at the following sample code using an early version of a pie chart widget and the output
it generates:
d = Drawing(400,200)
d.add(String(100,175,"Without labels", textAnchor="middle"))
d.add(String(300,175,"With labels", textAnchor="middle"))
pc = Pie()
pc.x = 25
pc.y = 50
pc.data = [10,20,30,40,50,60]
pc.slices[0].popout = 5
d.add(pc, 'pie1')
pc2 = Pie()
pc2.x = 150
pc2.y = 50
pc2.data = [10,20,30,40,50,60]
pc2.labels = ['a','b','c','d','e','f']
d.add(pc2, 'pie2')
Page 129
User Guide Chapter 11 Graphics
pc3 = Pie()
pc3.x = 275
pc3.y = 50
pc3.data = [10,20,30,40,50,60]
pc3.labels = ['a','b','c','d','e','f']
pc3.slices.labelRadius = 0.65
pc3.slices.fontName = "Helvetica-Bold"
pc3.slices.fontSize = 16
pc3.slices.fontColor = colors.yellow
d.add(pc3, 'pie3')
e d
d
e
Page 130
User Guide Appendix A ReportLab Demos
A.1 Odyssey
The three scripts odyssey.py, dodyssey.py and fodyssey.py all take the file odyssey.txt and produce PDF
documents. The included odyssey.txt is short; a longer and more testing version can be found at
ftp://ftp.reportlab.com/odyssey.full.zip.
Windows
cd reportlab\demos\odyssey
python odyssey.py
start odyssey.pdf
Linux
cd reportlab/demos/odyssey
python odyssey.py
acrord odyssey.pdf
Simple formatting is shown by the odyssey.py script. It runs quite fast, but all it does is gather the text and
force it onto the canvas pages. It does no paragraph manipulation at all so you get to see the XML < & > tags.
The scripts fodyssey.py and dodyssey.py handle paragraph formatting so you get to see colour changes etc.
Both scripts use the document template class and the dodyssey.py script shows the ability to do dual column
layout and uses multiple page templates.
cd reportlab\demos\stdfonts
python stdfonts.py
A.3 Py2pdf
Dinu Gherman contributed this useful script which uses reportlab to produce nicely colorized PDF
documents from Python scripts including bookmarks for classes, methods and functions. To get a nice
version of the main script try
cd reportlab/demos/py2pdf
python py2pdf.py py2pdf.py
acrord py2pdf.pdf
i.e. we used py2pdf to produce a nice version of py2pdf.py in the document with the same rootname and a
.pdf extension.
The py2pdf.py script has many options which are beyond the scope of this simple introduction; consult the
comments at the start of the script.
Page 131
User Guide Appendix A ReportLab Demos
A.4 Gadflypaper
The Python script, gfe.py, in reportlab/demos/gadflypaper uses an inline style of document
preparation. The script almost entirely produced by Aaron Watters produces a document describing Aaron's
gadfly in memory database for Python. To generate the document use
cd reportlab\gadflypaper
python gfe.py
start gfe.pdf
everything in the PDF document was produced by the script which is why this is an inline style of document
production. So, to produce a header followed by some text the script uses functions header and p which
take some text and append to a global story list.
header("Conclusion")
A.5 Pythonpoint
Andy Robinson has refined the pythonpoint.py script (in reportlab\demos\pythonpoint) until it is a
really useful script. It takes an input file containing an XML markup and uses an xmllib style parser to map
the tags into PDF slides. When run in its own directory pythonpoint.py takes as a default input the file
pythonpoint.xml and produces pythonpoint.pdf which is documentation for Pythonpoint! You can also see it
in action with an older paper
cd reportlab\demos\pythonpoint
python pythonpoint.py monterey.xml
start monterey.pdf
Not only is pythonpoint self documenting, but it also demonstrates reportlab and PDF. It uses many features
of reportlab (document templates, tables etc). Exotic features of PDF such as fadeins and bookmarks are also
shown to good effect. The use of an XML document can be contrasted with the inline style of the gadflypaper
demo; the content is completely separate from the formatting
Page 132