1
#Copyright ReportLab Europe Ltd. 2000-2004
2
#see license.txt for license details
3
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/canvas.py
4
__version__=''' $Id$ '''
6
The Canvas object is the primary interface for creating PDF files. See
7
doc/userguide.pdf for copious examples.
9
ENABLE_TRACKING = 1 # turn this off to do profile testing w/o tracking
13
from string import join, split, strip, atoi, replace, upper
16
from math import sin, cos, tan, pi, ceil
19
from reportlab import rl_config
20
from reportlab.pdfbase import pdfutils
21
from reportlab.pdfbase import pdfdoc
22
from reportlab.pdfbase import pdfmetrics
23
from reportlab.pdfgen import pdfgeom, pathobject, textobject
24
from reportlab.lib.colors import Color, CMYKColor, toColor
25
from reportlab.lib.utils import import_zlib
26
from reportlab.lib.utils import fp_str
29
_SeqTypes=(TupleType,ListType)
32
# Constants for closing paths.
33
# May be useful if one changes 'arc' and 'rect' to take a
34
# default argument that tells how to close the path.
35
# That way we can draw filled shapes.
39
#this is used by path-closing routines.
40
#map stroke, fill, fillmode -> operator
41
# fillmode: 1 = non-Zero (obviously), 0 = evenOdd
42
PATH_OPS = {(0, 0, FILL_EVEN_ODD) : 'n', #no op
43
(0, 0, FILL_NON_ZERO) : 'n', #no op
44
(1, 0, FILL_EVEN_ODD) : 'S', #stroke only
45
(1, 0, FILL_NON_ZERO) : 'S', #stroke only
46
(0, 1, FILL_EVEN_ODD) : 'f*', #Fill only
47
(0, 1, FILL_NON_ZERO) : 'f', #Fill only
48
(1, 1, FILL_EVEN_ODD) : 'B*', #Stroke and Fill
49
(1, 1, FILL_NON_ZERO) : 'B', #Stroke and Fill
52
_escapePDF = pdfutils._escape
53
_instanceEscapePDF = pdfutils._instanceEscapePDF
55
if sys.hexversion >= 0x02000000:
57
return md5.md5(s).hexdigest()
59
# hexdigest not available in 1.5
61
return join(map(lambda x : "%02x" % ord(x), md5.md5(s).digest()), '')
64
"""This class is the programmer's interface to the PDF file format. Methods
65
are (or will be) provided here to do just about everything PDF can do.
67
The underlying model to the canvas concept is that of a graphics state machine
68
that at any given point in time has a current font, fill color (for figure
69
interiors), stroke color (for figure borders), line width and geometric transform, among
70
many other characteristics.
72
Canvas methods generally either draw something (like canvas.line) using the
73
current state of the canvas or change some component of the canvas
74
state (like canvas.setFont). The current state can be saved and restored
75
using the saveState/restoreState methods.
77
Objects are "painted" in the order they are drawn so if, for example
78
two rectangles overlap the last draw will appear "on top". PDF form
79
objects (supported here) are used to draw complex drawings only once,
80
for possible repeated use.
82
There are other features of canvas which are not visible when printed,
83
such as outlines and bookmarks which are used for navigating a document
86
Here is a very silly example usage which generates a Hello World pdf document.
88
from reportlab.pdfgen import canvas
89
c = canvas.Canvas("hello.pdf")
90
from reportlab.lib.units import inch
91
# move the origin up and to the left
92
c.translate(inch,inch)
94
c.setFont("Helvetica", 80)
96
c.setStrokeColorRGB(0.2,0.5,0.3)
97
c.setFillColorRGB(1,0,1)
99
c.rect(inch,inch,6*inch,9*inch, fill=1)
100
# make text go straight up
103
c.setFillColorRGB(0,0,0.77)
104
# say hello (note after rotate the y coord needs to be negative!)
105
c.drawString(3*inch, -3*inch, "Hello World")
110
def __init__(self,filename,
113
pageCompression=None,
117
"""Create a canvas of a given size. etc.
118
Most of the attributes are private - we will use set/get methods
119
as the preferred interface. Default page size is A4."""
120
if pagesize is None: pagesize = rl_config.defaultPageSize
121
if encoding is None: encoding = rl_config.defaultEncoding
122
if invariant is None: invariant = rl_config.invariant
123
self._filename = filename
124
self._encodingName = encoding
125
self._doc = pdfdoc.PDFDocument(encoding,
126
compression=pageCompression,
130
#this only controls whether it prints 'saved ...' - 0 disables
131
self._verbosity = verbosity
133
#this is called each time a page is output if non-null
136
self._pagesize = pagesize
137
self._pageRotation = 0
138
#self._currentPageHasImages = 0
139
self._pageTransition = None
140
self._pageDuration = None
141
self._destinations = {} # dictionary of destinations for cross indexing.
143
self.setPageCompression(pageCompression)
144
self._pageNumber = 1 # keep a count
145
#self3 = [] #where the current page's marking operators accumulate
146
# when we create a form we need to save operations not in the form
148
self._restartAccumulators() # restart all accumulation state (generalized, arw)
149
self._annotationCount = 0
151
self._outlines = [] # list for a name tree
152
self._psCommandsBeforePage = [] #for postscript tray/font commands
153
self._psCommandsAfterPage = [] #for postscript tray/font commands
155
#PostScript has the origin at bottom left. It is easy to achieve a top-
156
#down coord system by translating to the top of the page and setting y
157
#scale to -1, but then text is inverted. So self.bottomup is used
158
#to also set the text matrix accordingly. You can now choose your
159
#drawing coordinates.
160
self.bottomup = bottomup
161
self.imageCaching = rl_config.defaultImageCaching
162
self._make_preamble()
163
self.init_graphics_state()
164
self.state_stack = []
166
def init_graphics_state(self):
167
#initial graphics state, never modify any of these in place
170
self._fontname = 'Times-Roman'
172
self._dynamicFont = 0
173
self._textMode = 0 #track if between BT/ET
175
self._currentMatrix = (1., 0., 0., 1., 0., 0.)
176
self._fillMode = 0 #even-odd
181
self._horizScale = 100
182
self._textRenderMode = 0
184
self._textLineMatrix = (1., 0., 0., 1., 0., 0.)
185
self._textMatrix = (1., 0., 0., 1., 0., 0.)
190
self._lineDash = None #not done
194
self._fillColorRGB = (0,0,0)
195
self._strokeColorRGB = (0,0,0)
197
def push_state_stack(self):
200
for name in self.STATE_ATTRIBUTES:
201
state[name] = d[name] #getattr(self, name)
202
self.state_stack.append(state)
204
def pop_state_stack(self):
205
state = self.state_stack[-1]
206
del self.state_stack[-1]
210
STATE_ATTRIBUTES = split("""
211
_x _y _fontname _fontsize _dynamicFont _textMode _leading _currentMatrix _fillMode
212
_fillMode _charSpace _wordSpace _horizScale _textRenderMode _rise _textLineMatrix
213
_textMatrix _lineCap _lineJoin _lineDash _lineWidth _mitreLimit _fillColorRGB
215
STATE_RANGE = range(len(STATE_ATTRIBUTES))
217
#self._addStandardFonts()
219
def _make_preamble(self):
221
iName = self._doc.getInternalFontName('Helvetica')
223
#must set an initial font
224
self._preamble = '1 0 0 1 0 0 cm BT %s 12 Tf 14.4 TL ET' % iName
226
#switch coordinates, flip text and set font
227
self._preamble = '1 0 0 -1 0 %s cm BT %s 12 Tf 14.4 TL ET' % (fp_str(self._pagesize[1]), iName)
229
if not _instanceEscapePDF:
230
def _escape(self, s):
233
#info functions - non-standard
234
def setAuthor(self, author):
235
"""identify the author for invisible embedding inside the PDF document.
236
the author annotation will appear in the the text of the file but will
237
not automatically be seen when the document is viewed."""
238
self._doc.setAuthor(author)
240
def addOutlineEntry(self, title, key, level=0, closed=None):
241
"""Adds a new entry to the outline at given level. If LEVEL not specified,
242
entry goes at the top level. If level specified, it must be
243
no more than 1 greater than the outline level in the last call.
245
The key must be the (unique) name of a bookmark.
246
the title is the (non-unique) name to be displayed for the entry.
248
If closed is set then the entry should show no subsections by default
252
c.addOutlineEntry("first section", "section1")
253
c.addOutlineEntry("introduction", "s1s1", 1, closed=1)
254
c.addOutlineEntry("body", "s1s2", 1)
255
c.addOutlineEntry("detail1", "s1s2s1", 2)
256
c.addOutlineEntry("detail2", "s1s2s2", 2)
257
c.addOutlineEntry("conclusion", "s1s3", 1)
258
c.addOutlineEntry("further reading", "s1s3s1", 2)
259
c.addOutlineEntry("second section", "section1")
260
c.addOutlineEntry("introduction", "s2s1", 1)
261
c.addOutlineEntry("body", "s2s2", 1, closed=1)
262
c.addOutlineEntry("detail1", "s2s2s1", 2)
263
c.addOutlineEntry("detail2", "s2s2s2", 2)
264
c.addOutlineEntry("conclusion", "s2s3", 1)
265
c.addOutlineEntry("further reading", "s2s3s1", 2)
267
generated outline looks like
281
Note that the second "body" is closed.
283
Note that you can jump from level 5 to level 3 but not
284
from 3 to 5: instead you need to provide all intervening
285
levels going down (4 in this case). Note that titles can
286
collide but keys cannot.
289
#self._outlines.append(title)
290
self._doc.outline.addOutlineEntry(key, level, title, closed=closed)
292
def setOutlineNames0(self, *nametree): # keep this for now (?)
293
"""nametree should can be a recursive tree like so
297
["chapter2section1dest",
298
"chapter2section2dest",
299
"chapter2conclusiondest"]
300
), # end of chapter2 description
302
("chapter4dest", ["c4s1", "c4s2"])
304
each of the string names inside must be bound to a bookmark
305
before the document is generated.
307
apply(self._doc.outline.setNames, (self,)+nametree)
309
def setTitle(self, title):
310
"""write a title into the PDF file that won't automatically display
311
in the document itself."""
312
self._doc.setTitle(title)
314
def setSubject(self, subject):
315
"""write a subject into the PDF file that won't automatically display
316
in the document itself."""
317
self._doc.setSubject(subject)
319
def pageHasData(self):
320
"Info function - app can call it after showPage to see if it needs a save"
321
return len(self._code) == 0
323
def showOutline(self):
324
"""Specify that Acrobat Reader should start with the outline tree visible.
325
showFullScreen() and showOutline() conflict; the one called last
327
self._doc._catalog.showOutline()
329
def showFullScreen0(self):
330
"""Specify that Acrobat Reader should start in full screen mode.
331
showFullScreen() and showOutline() conflict; the one called last
333
self._doc._catalog.showFullScreen()
336
"""Close the current page and possibly start on a new page."""
338
# ensure a space at the end of the stream - Acrobat does
339
# not mind, but Ghostscript dislikes 'Qendstream' even if
340
# the length marker finishes after 'Q'
341
self._code.append(' ')
342
page = pdfdoc.PDFPage()
343
page.pagewidth = self._pagesize[0]
344
page.pageheight = self._pagesize[1]
345
page.Rotate = self._pageRotation
346
page.hasImages = self._currentPageHasImages
347
page.setPageTransition(self._pageTransition)
348
page.setCompression(self._pageCompression)
349
if self._pageDuration is not None:
350
page.Dur = self._pageDuration
352
strm = self._psCommandsBeforePage + [self._preamble] + self._code + self._psCommandsAfterPage
354
self._setXObjects(page)
355
self._setAnnotations(page)
356
self._doc.addPage(page)
358
if self._onPage: self._onPage(self._pageNumber)
361
def _startPage(self):
362
#now get ready for the next one
363
self._pageNumber = self._pageNumber+1
364
self._restartAccumulators()
365
self.init_graphics_state()
366
self.state_stack = []
368
def setPageCallBack(self, func):
369
"""func(pageNum) will be called on each page end.
371
This is mainly a hook for progress monitoring.
372
Call setPageCallback(None) to clear a callback."""
375
def _setAnnotations(self,page):
376
page.Annots = self._annotationrefs
378
def _setXObjects(self, thing):
379
"""for pages and forms, define the XObject dictionary for resources, if needed"""
380
forms = self._formsinuse
382
xobjectsdict = self._doc.xobjDict(forms)
383
thing.XObjects = xobjectsdict
385
thing.XObjects = None
387
def _bookmarkReference(self, name):
388
"""get a reference to a (possibly undefined, possibly unbound) bookmark"""
389
d = self._destinations
393
result = d[name] = pdfdoc.Destination(name) # newly defined, unbound
396
def bookmarkPage(self, key,
405
This creates a bookmark to the current page which can
406
be referred to with the given key elsewhere.
408
PDF offers very fine grained control over how Acrobat
409
reader is zoomed when people link to this. The default
410
is to keep the user's current zoom settings. the last
411
arguments may or may not be needed depending on the
414
Fit types and the other arguments they use are:
415
/XYZ left top zoom - fine grained control. null
416
or zero for any of the parameters means 'leave
417
as is', so "0,0,0" will keep the reader's settings.
418
NB. Adobe Reader appears to prefer "null" to 0's.
420
/Fit - entire page fits in window
422
/FitH top - top coord at top of window, width scaled
425
/FitV left - left coord at left of window, height
428
/FitR left bottom right top - scale window to fit
429
the specified rectangle
431
(question: do we support /FitB, FitBH and /FitBV
432
which are hangovers from version 1.1 / Acrobat 3.0?)"""
433
dest = self._bookmarkReference(key)
434
self._doc.inPage() # try to enable page-only features
435
pageref = self._doc.thisPageRef()
437
#None = "null" for PDF
450
dest.xyz(left,top,zoom)
451
elif fitType == "Fit":
453
elif fitType == "FitH":
455
elif fitType == "FitV":
457
elif fitType == "FitR":
458
dest.fitr(left,bottom,right,top)
459
#Do we need these (version 1.1 / Acrobat 3 versions)?
460
elif fitType == "FitB":
462
elif fitType == "FitBH":
464
elif fitType == "FitBV":
467
raise "Unknown Fit type %s" % (fitType,)
469
dest.setPage(pageref)
472
def bookmarkHorizontalAbsolute(self, key, yhorizontal):
473
"""Bind a bookmark (destination) to the current page at a horizontal position.
474
Note that the yhorizontal of the book mark is with respect to the default
475
user space (where the origin is at the lower left corner of the page)
476
and completely ignores any transform (translation, scale, skew, rotation,
477
etcetera) in effect for the current graphics state. The programmer is
478
responsible for making sure the bookmark matches an appropriate item on
480
#This method should probably be deprecated since it is just a sub-set of bookmarkPage
481
return self.bookmarkPage(key,fitType="FitH",top=yhorizontal)
483
def bookmarkHorizontal(self, key, relativeX, relativeY):
484
"""w.r.t. the current transformation, bookmark this horizontal."""
485
(xt, yt) = self.absolutePosition(relativeX,relativeY)
486
self.bookmarkHorizontalAbsolute(key, yt)
488
#def _inPage0(self): disallowed!
489
# """declare a page, enable page features"""
493
# "deprecated in favore of beginForm...endForm"
496
def doForm(self, name):
497
"""use a form XObj in current operation stream.
499
The form should either have been defined previously using
500
beginForm ... endForm, or may be defined later. If it is not
501
defined at save time, an exception will be raised. The form
502
will be drawn within the context of the current graphics
504
self._code.append("/%s Do" % self._doc.getXObjectName(name))
505
self._formsinuse.append(name)
507
def hasForm(self, name):
508
"""Query whether form XObj really exists yet."""
509
return self._doc.hasForm(name)
512
######################################################
516
######################################################
518
def drawInlineImage(self, image, x,y, width=None,height=None):
519
"""Draw an Image into the specified rectangle. If width and
520
height are omitted, they are calculated from the image size.
521
Also allow file names as well as images. The size in pixels
522
of the image is returned."""
524
self._currentPageHasImages = 1
525
from pdfimages import PDFImage
526
img_obj = PDFImage(image, x,y, width, height)
527
img_obj.drawInlineImage(self)
528
return (img_obj.width, img_obj.height)
530
def drawImage(self, image, x, y, width=None, height=None, mask=None):
531
"""Draws the image (ImageReader object or filename) as specified.
533
"image" may be an image filename or a ImageReader object. If width
534
and height are not given, the "natural" width and height in pixels
535
is used at a scale of 1 point to 1 pixel.
537
The mask parameter takes 6 numbers and defines the range of
538
RGB values which will be masked out or treated as transparent.
539
For example with [0,2,40,42,136,139], it will mask out any
540
pixels with a Red value from 0-2, Green from 40-42 and
541
Blue from 136-139 (on a scale of 0-255)
543
The method returns the width and height of the underlying image since
544
this is often useful for layout algorithms.
546
Unlike drawInlineImage, this creates 'external images' which
547
are only stored once in the PDF file but can be drawn many times.
548
If you give it the same filename twice, even at different locations
549
and sizes, it will reuse the first occurrence. If you use ImageReader
550
objects, it tests whether the image content has changed before deciding
553
In general you should use drawImage in preference to drawInlineImage
554
unless you have read the PDF Spec and understand the tradeoffs."""
555
self._currentPageHasImages = 1
557
# first, generate a unique name/signature for the image. If ANYTHING
558
# is different, even the mask, this should be different.
559
if type(image) == type(''):
561
name = _digester('%s%s' % (image, mask))
563
rawdata = image.getRGBData()
564
name = _digester(rawdata+str(mask))
566
# in the pdf document, this will be prefixed with something to
567
# say it is an XObject. Does it exist yet?
568
regName = self._doc.getXObjectName(name)
569
imgObj = self._doc.idToObject.get(regName, None)
571
#first time seen, create and register the PDFImageXobject
572
imgObj = pdfdoc.PDFImageXObject(name, image, mask=mask)
574
self._setXObjects(imgObj)
575
self._doc.Reference(imgObj, regName)
576
self._doc.addForm(name, imgObj)
578
# ensure we have a size, as PDF will make it 1x1 pixel otherwise!
582
height = imgObj.height
587
self.scale(width, height)
588
self._code.append("/%s Do" % regName)
591
# track what's been used on this page
592
self._formsinuse.append(name)
594
return (imgObj.width, imgObj.height)
596
def _restartAccumulators(self):
598
# restore the saved code
599
saved = self._codeStack[-1]
600
del self._codeStack[-1]
601
(self._code, self._formsinuse, self._annotationrefs, self._formData) = saved
603
self._code = [] # ready for more...
604
self._psCommandsAfterPage = []
605
self._currentPageHasImages = 1 # for safety...
606
self._formsinuse = []
607
self._annotationrefs = []
608
self._formData = None
610
def _pushAccumulators(self):
611
"when you enter a form, save accumulator info not related to the form for page (if any)"
612
saved = (self._code, self._formsinuse, self._annotationrefs, self._formData)
613
self._codeStack.append(saved)
614
self._code = [] # ready for more...
615
self._currentPageHasImages = 1 # for safety...
616
self._formsinuse = []
617
self._annotationrefs = []
618
self._formData = None
620
def beginForm(self, name, lowerx=0, lowery=0, upperx=None, uppery=None):
621
"""declare the current graphics stream to be a named form.
622
A graphics stream can either be a page or a form, not both.
623
Some operations (like bookmarking) are permitted for pages
624
but not forms. The form will not automatically be shown in the
625
document but must be explicitly referenced using doForm in pages
626
that require the form."""
627
self.push_state_stack()
628
self.init_graphics_state()
630
# save the code that is not in the formf
631
self._pushAccumulators()
632
#self._codeStack.append(self._code)
634
self._formData = (name, lowerx, lowery, upperx, uppery)
639
"""emit the current collection of graphics operations as a Form
640
as declared previously in beginForm."""
641
(name, lowerx, lowery, upperx, uppery) = self._formData
642
#self.makeForm0(name, lowerx, lowery, upperx, uppery)
643
# fall through! makeForm0 disallowed
644
#def makeForm0(self, name, lowerx=0, lowery=0, upperx=None, uppery=None):
645
"""Like showpage, but make a form using accumulated operations instead"""
646
# deprecated in favor or beginForm(...)... endForm()
647
(w,h) = self._pagesize
648
if upperx is None: upperx=w
649
if uppery is None: uppery=h
650
form = pdfdoc.PDFFormXObject(lowerx=lowerx, lowery=lowery, upperx=upperx, uppery=uppery)
651
form.compression = self._pageCompression
652
form.setStreamList([self._preamble] + self._code) # ??? minus preamble (seems to be needed!)
653
self._setXObjects(form)
654
self._setAnnotations(form)
655
self._doc.addForm(name, form)
656
self._restartAccumulators()
657
self.pop_state_stack()
660
def addPostScriptCommand(self, command, position=1):
661
"""Embed literal Postscript in the document.
663
With position=0, it goes at very beginning of page stream;
664
with position=1, at current point; and
665
with position=2, at very end of page stream. What that does
666
to the resulting Postscript depends on Adobe's header :-)
668
Use with extreme caution, but sometimes needed for printer tray commands.
669
Acrobat 4.0 will export Postscript to a printer or file containing
670
the given commands. Adobe Reader 6.0 no longer does as this feature is
671
deprecated. 5.0, I don't know about (please let us know!). This was
672
funded by Bob Marshall of Vector.co.uk and tested on a Lexmark 750.
673
See test_pdfbase_postscript.py for 2 test cases - one will work on
674
any Postscript device, the other uses a 'setpapertray' command which
675
will error in Distiller but work on printers supporting it.
677
#check if we've done this one already...
678
rawName = 'PS' + md5.md5(command).hexdigest()
679
regName = self._doc.getXObjectName(rawName)
680
psObj = self._doc.idToObject.get(regName, None)
682
#first use of this chunk of Postscript, make an object
683
psObj = pdfdoc.PDFPostScriptXObject(command + '\r\n')
684
self._setXObjects(psObj)
685
self._doc.Reference(psObj, regName)
686
self._doc.addForm(rawName, psObj)
688
self._psCommandsBeforePage.append("/%s Do" % regName)
690
self._code.append("/%s Do" % regName)
692
self._psCommandsAfterPage.append("/%s Do" % regName)
694
self._formsinuse.append(rawName)
696
def textAnnotation0(self, contents, Rect=None, addtopage=1, name=None, **kw):
700
(w,h) = self._pagesize# default to whole page (?)
702
annotation = apply(pdfdoc.TextAnnotation, (Rect, contents), kw)
703
self._addAnnotation(annotation, name, addtopage)
705
def inkAnnotation0(self, contents, InkList=None, Rect=None, addtopage=1, name=None, **kw):
707
(w,h) = self._pagesize
711
InkList = ( (100,100,100,h-100,w-100,h-100,w-100,100), )
712
annotation = apply(pdfdoc.InkAnnotation, (Rect, contents, InkList), kw)
713
self.addAnnotation(annotation, name, addtopage)
715
def linkAbsolute(self, contents, destinationname, Rect=None, addtopage=1, name=None, **kw):
716
"""rectangular link annotation positioned wrt the default user space.
717
The identified rectangle on the page becomes a "hot link" which
718
when clicked will send the viewer to the page and position identified
721
Rect identifies (lowerx, lowery, upperx, uppery) for lower left
722
and upperright points of the rectangle. Translations and other transforms
723
are IGNORED (the rectangular position is given with respect
724
to the default user space.
725
destinationname should be the name of a bookmark (which may be defined later
726
but must be defined before the document is generated).
728
You may want to use the keyword argument Border='[0 0 0]' to
729
suppress the visible rectangle around the during viewing link."""
730
destination = self._bookmarkReference(destinationname) # permitted to be undefined... must bind later...
731
(w,h) = self._pagesize
735
kw["Contents"] = contents
736
kw["Destination"] = destination
737
annotation = apply(pdfdoc.LinkAnnotation, (), kw)
738
self._addAnnotation(annotation, name, addtopage)
740
def linkRect(self, contents, destinationname, Rect=None, addtopage=1, name=None, **kw):
741
"""rectangular link annotation w.r.t the current user transform.
742
if the transform is skewed/rotated the absolute rectangle will use the max/min x/y
745
pass # do whatever linkAbsolute does
747
(lx, ly, ux, uy) = Rect
748
(xll,yll) = self.absolutePosition(lx,ly)
749
(xur,yur) = self.absolutePosition(ux, uy)
750
(xul,yul) = self.absolutePosition(lx, uy)
751
(xlr,ylr) = self.absolutePosition(ux, ly)
752
xs = (xll, xur, xul, xlr)
753
ys = (yll, yur, yul, ylr)
754
(xmin, ymin) = (min(xs), min(ys))
755
(xmax, ymax) = (max(xs), max(ys))
756
#(w2, h2) = (xmax-xmin, ymax-ymin)
757
Rect = (xmin, ymin, xmax, ymax)
758
return apply(self.linkAbsolute, (contents, destinationname, Rect, addtopage, name), kw)
760
def linkURL(self, url, rect, relative=0, thickness=0, color=None, dashArray=None):
761
"""Create a rectangular URL 'hotspot' in the given rectangle.
763
if relative=1, this is in the current coord system, otherwise
764
in absolute page space.
765
The remaining options affect the border appearance; the border is
766
drawn by Acrobat, not us. Set thickness to zero to hide it.
767
Any border drawn this way is NOT part of the page stream and
768
will not show when printed to a Postscript printer or distilled;
769
it is safest to draw your own."""
770
from reportlab.pdfbase.pdfdoc import PDFDictionary, PDFName, PDFArray, PDFString
771
#tried the documented BS element in the pdf spec but it
772
#does not work, and Acrobat itself does not appear to use it!
775
(lx, ly, ux, uy) = rect
776
(xll,yll) = self.absolutePosition(lx,ly)
777
(xur,yur) = self.absolutePosition(ux, uy)
778
(xul,yul) = self.absolutePosition(lx, uy)
779
(xlr,ylr) = self.absolutePosition(ux, ly)
780
xs = (xll, xur, xul, xlr)
781
ys = (yll, yur, yul, ylr)
782
(xmin, ymin) = (min(xs), min(ys))
783
(xmax, ymax) = (max(xs), max(ys))
784
#(w2, h2) = (xmax-xmin, ymax-ymin)
785
rect = (xmin, ymin, xmax, ymax)
787
ann = PDFDictionary()
788
ann["Type"] = PDFName("Annot")
789
ann["Subtype"] = PDFName("Link")
790
ann["Rect"] = PDFArray(rect) # the whole page for testing
792
# the action is a separate dictionary
794
A["Type"] = PDFName("Action") # not needed?
795
A["S"] = PDFName("URI")
796
A["URI"] = PDFString(url)
799
# now for formatting stuff.
801
ann["C"] = PDFArray([color.red, color.green, color.blue])
804
border[2] = thickness
806
border.append(PDFArray(dashArray))
807
ann["Border"] = PDFArray(border)
809
self._addAnnotation(ann)
811
def _addAnnotation(self, annotation, name=None, addtopage=1):
812
count = self._annotationCount = self._annotationCount+1
813
if not name: name="NUMBER"+repr(count)
814
self._doc.addAnnotation(name, annotation)
816
self._annotatePage(name)
818
def _annotatePage(self, name):
819
ref = self._doc.refAnnotation(name)
820
self._annotationrefs.append(ref)
822
def getPageNumber(self):
823
"get the page number for the current page being generated."
824
return self._pageNumber
827
"""Saves and close the PDF document in the file.
828
If there is current data a ShowPage is executed automatically.
829
After this operation the canvas must not be used further."""
830
if len(self._code): self.showPage()
831
self._doc.SaveToFile(self._filename, self)
833
def getpdfdata(self):
834
"""Returns the PDF data that would normally be written to a file.
835
If there is current data a ShowPage is executed automatically.
836
After this operation the canvas must not be used further."""
837
if len(self._code): self.showPage()
838
return self._doc.GetPDFData(self)
840
def setPageSize(self, size):
841
"""accepts a 2-tuple in points for paper size for this
842
and subsequent pages"""
843
self._pagesize = size
844
self._make_preamble()
846
def setPageRotation(self, rot):
847
"""Instruct display device that this page is to be rotated"""
848
assert rot % 90.0 == 0.0, "Rotation must be a multiple of 90 degrees"
849
self._pageRotation = rot
851
def addLiteral(self, s, escaped=1):
852
"""introduce the literal text of PDF operations s into the current stream.
853
Only use this if you are an expert in the PDF file format."""
854
s = str(s) # make sure its a string
856
s = self._escape(s) # convert to string for safety
859
######################################################################
861
# coordinate transformations
863
######################################################################
864
def resetTransforms(self):
865
"""I want to draw something (eg, string underlines) w.r.t. the default user space.
866
Reset the matrix! This should be used usually as follows:
868
canv.resetTransforms()
869
...draw some stuff in default space coords...
870
canv.restoreState() # go back!
872
# we have to adjoin the inverse, since reset is not a basic operation (without save/restore)
873
(selfa, selfb, selfc, selfd, selfe, selff) = self._currentMatrix
874
det = selfa*selfd - selfc*selfb
877
resulte = (selfc*selff - selfd*selfe)/det
880
resultf = (selfe*selfb - selff*selfa)/det
881
self.transform(resulta, resultb, resultc, resultd, resulte, resultf)
883
def transform(self, a,b,c,d,e,f):
884
"""adjoin a mathematical transform to the current graphics state matrix.
885
Not recommended for beginners."""
886
#"""How can Python track this?"""
888
a0,b0,c0,d0,e0,f0 = self._currentMatrix
889
self._currentMatrix = (a0*a+c0*b, b0*a+d0*b,
890
a0*c+c0*d, b0*c+d0*d,
891
a0*e+c0*f+e0, b0*e+d0*f+f0)
892
if self._code and self._code[-1][-3:]==' cm':
893
L = split(self._code[-1])
894
a0, b0, c0, d0, e0, f0 = map(float,L[-7:-1])
895
s = len(L)>7 and join(L)+ ' %s cm' or '%s cm'
896
self._code[-1] = s % fp_str(a0*a+c0*b,b0*a+d0*b,a0*c+c0*d,b0*c+d0*d,a0*e+c0*f+e0,b0*e+d0*f+f0)
898
self._code.append('%s cm' % fp_str(a,b,c,d,e,f))
900
## (a,b,c,d,e,f) = self.Kolor
901
## self.Kolor = (f,a,b,c,d,e)
902
## self.setStrokeColorRGB(f,a,b)
903
## self.setFillColorRGB(f,a,b)
904
## self.line(-90,-1000,1,1); self.line(1000,-90,-1,1)
905
## self.drawString(0,0,"here")
906
## Kolor = (0, 0.5, 1, 0.25, 0.7, 0.3)
908
def absolutePosition(self, x, y):
909
"""return the absolute position of x,y in user space w.r.t. default user space"""
910
if not ENABLE_TRACKING:
911
raise ValueError, "tracking not enabled! (canvas.ENABLE_TRACKING=0)"
912
(a,b,c,d,e,f) = self._currentMatrix
917
def translate(self, dx, dy):
918
"""move the origin from the current (0,0) point to the (dx,dy) point
919
(with respect to the current graphics state)."""
920
self.transform(1,0,0,1,dx,dy)
922
def scale(self, x, y):
923
"""Scale the horizontal dimension by x and the vertical by y
924
(with respect to the current graphics state).
925
For example canvas.scale(2.0, 0.5) will make everything short and fat."""
926
self.transform(x,0,0,y,0,0)
928
def rotate(self, theta):
929
"""Canvas.rotate(theta)
931
Rotate the canvas by the angle theta (in degrees)."""
932
c = cos(theta * pi / 180)
933
s = sin(theta * pi / 180)
934
self.transform(c, s, -s, c, 0, 0)
936
def skew(self, alpha, beta):
937
tanAlpha = tan(alpha * pi / 180)
938
tanBeta = tan(beta * pi / 180)
939
self.transform(1, tanAlpha, tanBeta, 1, 0, 0)
941
######################################################################
943
# graphics state management
945
######################################################################
948
"""Save the current graphics state to be restored later by restoreState.
951
canvas.setFont("Helvetica", 20)
954
canvas.setFont("Courier", 9)
956
canvas.restoreState()
957
# if the save/restore pairs match then font is Helvetica 20 again.
959
self.push_state_stack()
960
self._code.append('q')
962
def restoreState(self):
963
"""restore the graphics state to the matching saved state (see saveState)."""
964
self._code.append('Q')
965
self.pop_state_stack()
967
###############################################################
969
# Drawing methods. These draw things directly without
970
# fiddling around with Path objects. We can add any geometry
971
# methods we wish as long as their meaning is precise and
972
# they are of general use.
974
# In general there are two patterns. Closed shapes
975
# have the pattern shape(self, args, stroke=1, fill=0);
976
# by default they draw an outline only. Line segments come
977
# in three flavours: line, bezier, arc (which is a segment
978
# of an elliptical arc, approximated by up to four bezier
979
# curves, one for each quadrant.
981
# In the case of lines, we provide a 'plural' to unroll
982
# the inner loop; it is useful for drawing big grids
983
################################################################
985
#--------first the line drawing methods-----------------------
987
def line(self, x1,y1, x2,y2):
988
"""draw a line segment from (x1,y1) to (x2,y2) (with color, thickness and
989
other attributes determined by the current graphics state)."""
990
self._code.append('n %s m %s l S' % (fp_str(x1, y1), fp_str(x2, y2)))
992
def lines(self, linelist):
993
"""Like line(), permits many lines to be drawn in one call.
994
for example for the figure
999
crosshairs = [(20,0,20,10), (20,30,20,40), (0,20,10,20), (30,20,40,20)]
1000
canvas.lines(crosshairs)
1002
self._code.append('n')
1003
for (x1,y1,x2,y2) in linelist:
1004
self._code.append('%s m %s l' % (fp_str(x1, y1), fp_str(x2, y2)))
1005
self._code.append('S')
1007
def grid(self, xlist, ylist):
1008
"""Lays out a grid in current line style. Supply list of
1009
x an y positions."""
1010
assert len(xlist) > 1, "x coordinate list must have 2+ items"
1011
assert len(ylist) > 1, "y coordinate list must have 2+ items"
1013
y0, y1 = ylist[0], ylist[-1]
1014
x0, x1 = xlist[0], xlist[-1]
1016
lines.append((x,y0,x,y1))
1018
lines.append((x0,y,x1,y))
1021
def bezier(self, x1, y1, x2, y2, x3, y3, x4, y4):
1022
"Bezier curve with the four given control points"
1023
self._code.append('n %s m %s c S' %
1024
(fp_str(x1, y1), fp_str(x2, y2, x3, y3, x4, y4))
1026
def arc(self, x1,y1, x2,y2, startAng=0, extent=90):
1027
"""Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
1028
starting at startAng degrees and covering extent degrees. Angles
1029
start with 0 to the right (+x) and increase counter-clockwise.
1030
These should have x1<x2 and y1<y2.
1032
Contributed to piddlePDF by Robert Kern, 28/7/99.
1033
Trimmed down by AR to remove color stuff for pdfgen.canvas and
1034
revert to positive coordinates.
1036
The algorithm is an elliptical generalization of the formulae in
1037
Jim Fitzsimmon's TeX tutorial <URL: http://www.tinaja.com/bezarc1.pdf>."""
1039
pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
1040
#move to first point
1041
self._code.append('n %s m' % fp_str(pointList[0][:2]))
1042
for curve in pointList:
1043
self._code.append('%s c' % fp_str(curve[2:]))
1045
self._code.append('S')
1047
#--------now the shape drawing methods-----------------------
1049
def rect(self, x, y, width, height, stroke=1, fill=0):
1050
"draws a rectangle with lower left corner at (x,y) and width and height as given."
1051
self._code.append('n %s re ' % fp_str(x, y, width, height)
1052
+ PATH_OPS[stroke, fill, self._fillMode])
1054
def ellipse(self, x1, y1, x2, y2, stroke=1, fill=0):
1055
"""Draw an ellipse defined by an enclosing rectangle.
1057
Note that (x1,y1) and (x2,y2) are the corner points of
1058
the enclosing rectangle.
1060
Uses bezierArc, which conveniently handles 360 degrees.
1061
Special thanks to Robert Kern."""
1063
pointList = pdfgeom.bezierArc(x1,y1, x2,y2, 0, 360)
1064
#move to first point
1065
self._code.append('n %s m' % fp_str(pointList[0][:2]))
1066
for curve in pointList:
1067
self._code.append('%s c' % fp_str(curve[2:]))
1069
self._code.append(PATH_OPS[stroke, fill, self._fillMode])
1071
def wedge(self, x1,y1, x2,y2, startAng, extent, stroke=1, fill=0):
1072
"""Like arc, but connects to the centre of the ellipse.
1073
Most useful for pie charts and PacMan!"""
1077
pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
1079
self._code.append('n %s m' % fp_str(x_cen, y_cen))
1080
# Move the pen to the center of the rectangle
1081
self._code.append('%s l' % fp_str(pointList[0][:2]))
1082
for curve in pointList:
1083
self._code.append('%s c' % fp_str(curve[2:]))
1085
self._code.append('%s l ' % fp_str(x_cen, y_cen))
1087
self._code.append(PATH_OPS[stroke, fill, self._fillMode])
1089
def circle(self, x_cen, y_cen, r, stroke=1, fill=0):
1090
"""draw a cirle centered at (x_cen,y_cen) with radius r (special case of ellipse)"""
1096
self.ellipse(x1, y1, x2, y2, stroke, fill)
1098
def roundRect(self, x, y, width, height, radius, stroke=1, fill=0):
1099
"""Draws a rectangle with rounded corners. The corners are
1100
approximately quadrants of a circle, with the given radius."""
1101
#use a precomputed set of factors for the bezier approximation
1102
#to a circle. There are six relevant points on the x axis and y axis.
1103
#sketch them and it should all make sense!
1109
x3 = x0 + width - radius
1116
y3 = y0 + height - radius
1117
y4 = y0 + height - t
1120
self._code.append('n %s m' % fp_str(x2, y0))
1121
self._code.append('%s l' % fp_str(x3, y0)) # bottom row
1122
self._code.append('%s c'
1123
% fp_str(x4, y0, x5, y1, x5, y2)) # bottom right
1125
self._code.append('%s l' % fp_str(x5, y3)) # right edge
1126
self._code.append('%s c'
1127
% fp_str(x5, y4, x4, y5, x3, y5)) # top right
1129
self._code.append('%s l' % fp_str(x2, y5)) # top row
1130
self._code.append('%s c'
1131
% fp_str(x1, y5, x0, y4, x0, y3)) # top left
1133
self._code.append('%s l' % fp_str(x0, y2)) # left edge
1134
self._code.append('%s c'
1135
% fp_str(x0, y1, x1, y0, x2, y0)) # bottom left
1137
self._code.append('h') #close off, although it should be where it started anyway
1139
self._code.append(PATH_OPS[stroke, fill, self._fillMode])
1141
##################################################
1145
# As with graphics, a separate object ensures that
1146
# everything is bracketed between text operators.
1147
# The methods below are a high-level convenience.
1148
# use PDFTextObject for multi-line text.
1149
##################################################
1151
def setFillColorCMYK(self, c, m, y, k):
1152
"""set the fill color useing negative color values
1153
(cyan, magenta, yellow and darkness value).
1154
Takes 4 arguments between 0.0 and 1.0"""
1155
self._fillColorCMYK = (c, m, y, k)
1156
self._code.append('%s k' % fp_str(c, m, y, k))
1158
def setStrokeColorCMYK(self, c, m, y, k):
1159
"""set the stroke color useing negative color values
1160
(cyan, magenta, yellow and darkness value).
1161
Takes 4 arguments between 0.0 and 1.0"""
1162
self._strokeColorCMYK = (c, m, y, k)
1163
self._code.append('%s K' % fp_str(c, m, y, k))
1165
def drawString(self, x, y, text):
1166
"""Draws a string in the current text styles."""
1167
#we could inline this for speed if needed
1168
t = self.beginText(x, y)
1172
def drawRightString(self, x, y, text):
1173
"""Draws a string right-aligned with the x coordinate"""
1174
width = self.stringWidth(text, self._fontname, self._fontsize)
1175
t = self.beginText(x - width, y)
1179
def drawCentredString(self, x, y, text):
1180
"""Draws a string centred on the x coordinate."""
1181
width = self.stringWidth(text, self._fontname, self._fontsize)
1182
t = self.beginText(x - 0.5*width, y)
1186
def drawAlignedString(self, x, y, text, pivotChar="."):
1187
"""Draws a string aligned on the first '.' (or other pivot character).
1189
The centre position of the pivot character will be used as x."""
1190
parts = text.split(pivotChar,1)
1191
pivW = self.stringWidth(pivotChar, self._fontname, self._fontsize)
1193
self.drawRightString(x-0.5*pivW, y, leftText)
1195
rightText = pivotChar + parts[1]
1196
self.drawString(x-0.5*pivW, y, rightText)
1198
def getAvailableFonts(self):
1199
"""Returns the list of PostScript font names available.
1201
Standard set now, but may grow in future with font embedding."""
1202
fontnames = self._doc.getAvailableFonts()
1206
def addFont(self, fontObj):
1207
"add a new font for subsequent use."
1208
self._doc.addFont(fontObj)
1210
def _addStandardFonts(self):
1211
"""Ensures the standard 14 fonts are available in the system encoding.
1212
Called by canvas on initialization"""
1213
for fontName in pdfmetrics.standardFonts:
1214
self.addFont(pdfmetrics.fontsByName[fontName])
1216
def listLoadedFonts0(self):
1217
"Convenience function to list all loaded fonts"
1218
names = pdfmetrics.widths.keys()
1222
def setFont(self, psfontname, size, leading = None):
1223
"""Sets the font. If leading not specified, defaults to 1.2 x
1224
font size. Raises a readable exception if an illegal font
1225
is supplied. Font names are case-sensitive! Keeps track
1226
of font name and size for metrics."""
1227
self._fontname = psfontname
1228
self._fontsize = size
1230
leading = size * 1.2
1231
self._leading = leading
1232
font = pdfmetrics.getFont(self._fontname)
1233
self._dynamicFont = getattr(font, '_dynamicFont', 0)
1234
if not self._dynamicFont:
1235
pdffontname = self._doc.getInternalFontName(psfontname)
1236
self._code.append('BT %s %s Tf %s TL ET' % (pdffontname, fp_str(size), fp_str(leading)))
1238
def stringWidth(self, text, fontName, fontSize, encoding=None):
1239
"gets width of a string in the given font and size"
1240
if encoding is not None:
1241
from reportlab.lib import logger
1242
logger.warnOnce('encoding argument to Canvas.stringWidth is deprecated and has no effect!')
1243
#if encoding is None: encoding = self._doc.encoding
1244
return pdfmetrics.stringWidth(text, fontName, fontSize)
1246
# basic graphics modes
1248
def setLineWidth(self, width):
1249
self._lineWidth = width
1250
self._code.append('%s w' % fp_str(width))
1252
def setLineCap(self, mode):
1253
"""0=butt,1=round,2=square"""
1254
assert mode in (0,1,2), "Line caps allowed: 0=butt,1=round,2=square"
1255
self._lineCap = mode
1256
self._code.append('%d J' % mode)
1258
def setLineJoin(self, mode):
1259
"""0=mitre, 1=round, 2=bevel"""
1260
assert mode in (0,1,2), "Line Joins allowed: 0=mitre, 1=round, 2=bevel"
1261
self._lineJoin = mode
1262
self._code.append('%d j' % mode)
1264
def setMiterLimit(self, limit):
1265
self._miterLimit = limit
1266
self._code.append('%s M' % fp_str(limit))
1268
def setDash(self, array=[], phase=0):
1269
"""Two notations. pass two numbers, or an array and phase"""
1270
if type(array) == IntType or type(array) == FloatType:
1271
self._code.append('[%s %s] 0 d' % (array, phase))
1272
elif type(array) == ListType or type(array) == TupleType:
1273
assert phase >= 0, "phase is a length in user space"
1274
textarray = join(map(str, array))
1275
self._code.append('[%s] %s d' % (textarray, phase))
1277
def setFillColorRGB(self, r, g, b):
1278
"""Set the fill color using positive color description
1279
(Red,Green,Blue). Takes 3 arguments between 0.0 and 1.0"""
1280
self._fillColorRGB = (r, g, b)
1281
self._code.append('%s rg' % fp_str(r,g,b))
1283
def setStrokeColorRGB(self, r, g, b):
1284
"""Set the stroke color using positive color description
1285
(Red,Green,Blue). Takes 3 arguments between 0.0 and 1.0"""
1286
self._strokeColorRGB = (r, g, b)
1287
self._code.append('%s RG' % fp_str(r,g,b))
1289
def setFillColor(self, aColor):
1290
"""Takes a color object, allowing colors to be referred to by name"""
1291
if isinstance(aColor, CMYKColor):
1293
c,m,y,k = (d*aColor.cyan, d*aColor.magenta, d*aColor.yellow, d*aColor.black)
1294
self._fillColorCMYK = (c, m, y, k)
1295
self._code.append('%s k' % fp_str(c, m, y, k))
1296
elif isinstance(aColor, Color):
1297
rgb = (aColor.red, aColor.green, aColor.blue)
1298
self._fillColorRGB = rgb
1299
self._code.append('%s rg' % fp_str(rgb) )
1300
elif type(aColor) in _SeqTypes:
1303
self._fillColorRGB = aColor
1304
self._code.append('%s rg' % fp_str(aColor) )
1306
self.setFillColorCMYK(aColor[0], aColor[1], aColor[2], aColor[3])
1308
raise 'Unknown color', str(aColor)
1309
elif type(aColor) is StringType:
1310
self.setFillColor(toColor(aColor))
1312
raise 'Unknown color', str(aColor)
1314
def setStrokeColor(self, aColor):
1315
"""Takes a color object, allowing colors to be referred to by name"""
1316
if isinstance(aColor, CMYKColor):
1318
c,m,y,k = (d*aColor.cyan, d*aColor.magenta, d*aColor.yellow, d*aColor.black)
1319
self._strokeColorCMYK = (c, m, y, k)
1320
self._code.append('%s K' % fp_str(c, m, y, k))
1321
elif isinstance(aColor, Color):
1322
#if type(aColor) == ColorType:
1323
rgb = (aColor.red, aColor.green, aColor.blue)
1324
self._strokeColorRGB = rgb
1325
self._code.append('%s RG' % fp_str(rgb) )
1326
elif type(aColor) in _SeqTypes:
1329
self._strokeColorRGB = aColor
1330
self._code.append('%s RG' % fp_str(aColor) )
1332
self.setStrokeColorCMYK(aColor[0], aColor[1], aColor[2], aColor[3])
1334
raise 'Unknown color', str(aColor)
1335
elif type(aColor) is StringType:
1336
self.setStrokeColor(toColor(aColor))
1338
raise 'Unknown color', str(aColor)
1340
def setFillGray(self, gray):
1341
"""Sets the gray level; 0.0=black, 1.0=white"""
1342
self._fillColorRGB = (gray, gray, gray)
1343
self._code.append('%s g' % fp_str(gray))
1345
def setStrokeGray(self, gray):
1346
"""Sets the gray level; 0.0=black, 1.0=white"""
1347
self._strokeColorRGB = (gray, gray, gray)
1348
self._code.append('%s G' % fp_str(gray))
1350
# path stuff - the separate path object builds it
1352
def beginPath(self):
1353
"""Returns a fresh path object. Paths are used to draw
1354
complex figures. The object returned follows the protocol
1355
for a pathobject.PDFPathObject instance"""
1356
return pathobject.PDFPathObject()
1358
def drawPath(self, aPath, stroke=1, fill=0):
1359
"Draw the path object in the mode indicated"
1360
gc = aPath.getCode(); pathops = PATH_OPS[stroke, fill, self._fillMode]
1361
item = "%s %s" % (gc, pathops) # ENSURE STRING CONVERSION
1362
self._code.append(item)
1363
#self._code.append(aPath.getCode() + ' ' + PATH_OPS[stroke, fill, self._fillMode])
1365
def clipPath(self, aPath, stroke=1, fill=0):
1366
"clip as well as drawing"
1367
gc = aPath.getCode(); pathops = PATH_OPS[stroke, fill, self._fillMode]
1368
clip = (self._fillMode == FILL_EVEN_ODD and ' W* ' or ' W ')
1369
item = "%s%s%s" % (gc, clip, pathops) # ensure string conversion
1370
self._code.append(item)
1371
#self._code.append( aPath.getCode()
1372
# + (self._fillMode == FILL_EVEN_ODD and ' W* ' or ' W ')
1373
# + PATH_OPS[stroke,fill,self._fillMode])
1375
def beginText(self, x=0, y=0):
1376
"""Returns a fresh text object. Text objects are used
1377
to add large amounts of text. See textobject.PDFTextObject"""
1378
return textobject.PDFTextObject(self, x, y)
1380
def drawText(self, aTextObject):
1381
"""Draws a text object"""
1382
self._code.append(str(aTextObject.getCode()))
1384
def setPageCompression(self, pageCompression=1):
1385
"""Possible values None, 1 or 0
1386
If None the value from rl_config will be used.
1387
If on, the page data will be compressed, leading to much
1388
smaller files, but takes a little longer to create the files.
1389
This applies to all subsequent pages, or until setPageCompression()
1391
if pageCompression is None: pageCompression = rl_config.pageCompression
1392
if pageCompression and not zlib:
1393
self._pageCompression = 0
1395
self._pageCompression = pageCompression
1396
self._doc.setCompression(self._pageCompression)
1398
def setPageDuration(self, duration=None):
1399
"""Allows hands-off animation of presentations :-)
1401
If this is set to a number, in full screen mode, Acrobat Reader
1402
will advance to the next page after this many seconds. The
1403
duration of the transition itself (fade/flicker etc.) is controlled
1404
by the 'duration' argument to setPageTransition; this controls
1405
the time spent looking at the page. This is effective for all
1406
subsequent pages."""
1408
self._pageDuration = duration
1410
def setPageTransition(self, effectname=None, duration=1,
1411
direction=0,dimension='H',motion='I'):
1412
"""PDF allows page transition effects for use when giving
1413
presentations. There are six possible effects. You can
1414
just guive the effect name, or supply more advanced options
1415
to refine the way it works. There are three types of extra
1416
argument permitted, and here are the allowed values:
1417
direction_arg = [0,90,180,270]
1418
dimension_arg = ['H', 'V']
1419
motion_arg = ['I','O'] (start at inside or outside)
1421
This table says which ones take which arguments:
1423
PageTransitionEffects = {
1424
'Split': [direction_arg, motion_arg],
1425
'Blinds': [dimension_arg],
1426
'Box': [motion_arg],
1427
'Wipe' : [direction_arg],
1429
'Glitter':[direction_arg]
1433
# This builds a Python dictionary with the right arguments
1434
# for the Trans dictionary in the PDFPage object,
1435
# and stores it in the variable _pageTransition.
1436
# showPage later passes this to the setPageTransition method
1437
# of the PDFPage object, which turns it to a PDFDictionary.
1438
self._pageTransition = {}
1442
#first check each optional argument has an allowed value
1443
if direction in [0,90,180,270]:
1444
direction_arg = ('Di', '/%d' % direction)
1446
raise 'PDFError', ' directions allowed are 0,90,180,270'
1448
if dimension in ['H', 'V']:
1449
dimension_arg = ('Dm', '/' + dimension)
1451
raise'PDFError','dimension values allowed are H and V'
1453
if motion in ['I','O']:
1454
motion_arg = ('M', '/' + motion)
1456
raise'PDFError','motion values allowed are I and O'
1458
# this says which effects require which argument types from above
1459
PageTransitionEffects = {
1460
'Split': [direction_arg, motion_arg],
1461
'Blinds': [dimension_arg],
1462
'Box': [motion_arg],
1463
'Wipe' : [direction_arg],
1465
'Glitter':[direction_arg]
1469
args = PageTransitionEffects[effectname]
1471
raise 'PDFError', 'Unknown Effect Name "%s"' % effectname
1473
# now build the dictionary
1475
transDict['Type'] = '/Trans'
1476
transDict['D'] = '%d' % duration
1477
transDict['S'] = '/' + effectname
1478
for (key, value) in args:
1479
transDict[key] = value
1480
self._pageTransition = transDict
1482
if _instanceEscapePDF:
1484
Canvas._escape = new.instancemethod(_instanceEscapePDF,None,Canvas)
1486
if __name__ == '__main__':
1487
print 'For test scripts, look in reportlab/test'