~openerp-groupes/openobject-server/6.0-fix-setup-windows

« back to all changes in this revision

Viewing changes to bin/reportlab/pdfgen/canvas.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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$ '''
 
5
__doc__="""
 
6
The Canvas object is the primary interface for creating PDF files. See
 
7
doc/userguide.pdf for copious examples.
 
8
"""
 
9
ENABLE_TRACKING = 1 # turn this off to do profile testing w/o tracking
 
10
 
 
11
import os
 
12
import sys
 
13
from string import join, split, strip, atoi, replace, upper
 
14
import tempfile
 
15
from types import *
 
16
from math import sin, cos, tan, pi, ceil
 
17
import md5
 
18
 
 
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
 
27
 
 
28
zlib = import_zlib()
 
29
_SeqTypes=(TupleType,ListType)
 
30
 
 
31
# Robert Kern
 
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.
 
36
 
 
37
FILL_EVEN_ODD = 0
 
38
FILL_NON_ZERO = 1
 
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
 
50
            }
 
51
 
 
52
_escapePDF = pdfutils._escape
 
53
_instanceEscapePDF = pdfutils._instanceEscapePDF
 
54
 
 
55
if sys.hexversion >= 0x02000000:
 
56
    def _digester(s):
 
57
        return md5.md5(s).hexdigest()
 
58
else:
 
59
    # hexdigest not available in 1.5
 
60
    def _digester(s):
 
61
        return join(map(lambda x : "%02x" % ord(x), md5.md5(s).digest()), '')
 
62
 
 
63
class Canvas:
 
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.
 
66
 
 
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.
 
71
 
 
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.
 
76
 
 
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.
 
81
 
 
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
 
84
    in a viewer.
 
85
 
 
86
    Here is a very silly example usage which generates a Hello World pdf document.
 
87
 
 
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)
 
93
    # define a large font
 
94
    c.setFont("Helvetica", 80)
 
95
    # choose some colors
 
96
    c.setStrokeColorRGB(0.2,0.5,0.3)
 
97
    c.setFillColorRGB(1,0,1)
 
98
    # draw a rectangle
 
99
    c.rect(inch,inch,6*inch,9*inch, fill=1)
 
100
    # make text go straight up
 
101
    c.rotate(90)
 
102
    # change color
 
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")
 
106
    c.showPage()
 
107
    c.save()
 
108
    """
 
109
 
 
110
    def __init__(self,filename,
 
111
                 pagesize=None,
 
112
                 bottomup = 1,
 
113
                 pageCompression=None,
 
114
                 encoding = None,
 
115
                 invariant = None,
 
116
                 verbosity=0):
 
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,
 
127
                                       invariant=invariant)
 
128
 
 
129
 
 
130
        #this only controls whether it prints 'saved ...' - 0 disables
 
131
        self._verbosity = verbosity
 
132
 
 
133
        #this is called each time a page is output if non-null
 
134
        self._onPage = None
 
135
 
 
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.
 
142
 
 
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
 
147
        self._codeStack = []
 
148
        self._restartAccumulators()  # restart all accumulation state (generalized, arw)
 
149
        self._annotationCount = 0
 
150
 
 
151
        self._outlines = [] # list for a name tree
 
152
        self._psCommandsBeforePage = [] #for postscript tray/font commands
 
153
        self._psCommandsAfterPage = [] #for postscript tray/font commands
 
154
 
 
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 = []
 
165
 
 
166
    def init_graphics_state(self):
 
167
        #initial graphics state, never modify any of these in place
 
168
        self._x = 0
 
169
        self._y = 0
 
170
        self._fontname = 'Times-Roman'
 
171
        self._fontsize = 12
 
172
        self._dynamicFont = 0
 
173
        self._textMode = 0  #track if between BT/ET
 
174
        self._leading = 14.4
 
175
        self._currentMatrix = (1., 0., 0., 1., 0., 0.)
 
176
        self._fillMode = 0   #even-odd
 
177
 
 
178
        #text state
 
179
        self._charSpace = 0
 
180
        self._wordSpace = 0
 
181
        self._horizScale = 100
 
182
        self._textRenderMode = 0
 
183
        self._rise = 0
 
184
        self._textLineMatrix = (1., 0., 0., 1., 0., 0.)
 
185
        self._textMatrix = (1., 0., 0., 1., 0., 0.)
 
186
 
 
187
        # line drawing
 
188
        self._lineCap = 0
 
189
        self._lineJoin = 0
 
190
        self._lineDash = None  #not done
 
191
        self._lineWidth = 0
 
192
        self._mitreLimit = 0
 
193
 
 
194
        self._fillColorRGB = (0,0,0)
 
195
        self._strokeColorRGB = (0,0,0)
 
196
 
 
197
    def push_state_stack(self):
 
198
        state = {}
 
199
        d = self.__dict__
 
200
        for name in self.STATE_ATTRIBUTES:
 
201
            state[name] = d[name] #getattr(self, name)
 
202
        self.state_stack.append(state)
 
203
 
 
204
    def pop_state_stack(self):
 
205
        state = self.state_stack[-1]
 
206
        del self.state_stack[-1]
 
207
        d = self.__dict__
 
208
        d.update(state)
 
209
 
 
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
 
214
     _strokeColorRGB""")
 
215
    STATE_RANGE = range(len(STATE_ATTRIBUTES))
 
216
 
 
217
        #self._addStandardFonts()
 
218
 
 
219
    def _make_preamble(self):
 
220
        # yuk
 
221
        iName = self._doc.getInternalFontName('Helvetica')
 
222
        if self.bottomup:
 
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
 
225
        else:
 
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)
 
228
 
 
229
    if not _instanceEscapePDF:
 
230
        def _escape(self, s):
 
231
            return _escapePDF(s)
 
232
 
 
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)
 
239
 
 
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.
 
244
 
 
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.
 
247
 
 
248
        If closed is set then the entry should show no subsections by default
 
249
        when displayed.
 
250
 
 
251
        Example
 
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)
 
266
 
 
267
        generated outline looks like
 
268
            - first section
 
269
            |- introduction
 
270
            |- body
 
271
            |  |- detail1
 
272
            |  |- detail2
 
273
            |- conclusion
 
274
            |  |- further reading
 
275
            - second section
 
276
            |- introduction
 
277
            |+ body
 
278
            |- conclusion
 
279
            |  |- further reading
 
280
 
 
281
        Note that the second "body" is closed.
 
282
 
 
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.
 
287
        """
 
288
        #to be completed
 
289
        #self._outlines.append(title)
 
290
        self._doc.outline.addOutlineEntry(key, level, title, closed=closed)
 
291
 
 
292
    def setOutlineNames0(self, *nametree):   # keep this for now (?)
 
293
        """nametree should can be a recursive tree like so
 
294
           c.setOutlineNames(
 
295
             "chapter1dest",
 
296
             ("chapter2dest",
 
297
              ["chapter2section1dest",
 
298
               "chapter2section2dest",
 
299
               "chapter2conclusiondest"]
 
300
             ), # end of chapter2 description
 
301
             "chapter3dest",
 
302
             ("chapter4dest", ["c4s1", "c4s2"])
 
303
             )
 
304
          each of the string names inside must be bound to a bookmark
 
305
          before the document is generated.
 
306
        """
 
307
        apply(self._doc.outline.setNames, (self,)+nametree)
 
308
 
 
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)
 
313
 
 
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)
 
318
 
 
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
 
322
 
 
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
 
326
        wins."""
 
327
        self._doc._catalog.showOutline()
 
328
 
 
329
    def showFullScreen0(self):
 
330
        """Specify that Acrobat Reader should start in full screen mode.
 
331
        showFullScreen() and showOutline() conflict; the one called last
 
332
        wins."""
 
333
        self._doc._catalog.showFullScreen()
 
334
 
 
335
    def showPage(self):
 
336
        """Close the current page and possibly start on a new page."""
 
337
 
 
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
 
351
 
 
352
        strm =  self._psCommandsBeforePage + [self._preamble] + self._code + self._psCommandsAfterPage
 
353
        page.setStream(strm)
 
354
        self._setXObjects(page)
 
355
        self._setAnnotations(page)
 
356
        self._doc.addPage(page)
 
357
 
 
358
        if self._onPage: self._onPage(self._pageNumber)
 
359
        self._startPage()
 
360
 
 
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 = []
 
367
 
 
368
    def setPageCallBack(self, func):
 
369
        """func(pageNum) will be called on each page end.
 
370
 
 
371
       This is mainly a hook for progress monitoring.
 
372
        Call setPageCallback(None) to clear a callback."""
 
373
        self._onPage = func
 
374
 
 
375
    def _setAnnotations(self,page):
 
376
        page.Annots = self._annotationrefs
 
377
 
 
378
    def _setXObjects(self, thing):
 
379
        """for pages and forms, define the XObject dictionary for resources, if needed"""
 
380
        forms = self._formsinuse
 
381
        if forms:
 
382
            xobjectsdict = self._doc.xobjDict(forms)
 
383
            thing.XObjects = xobjectsdict
 
384
        else:
 
385
            thing.XObjects = None
 
386
 
 
387
    def _bookmarkReference(self, name):
 
388
        """get a reference to a (possibly undefined, possibly unbound) bookmark"""
 
389
        d = self._destinations
 
390
        try:
 
391
            return d[name]
 
392
        except:
 
393
            result = d[name] = pdfdoc.Destination(name) # newly defined, unbound
 
394
        return result
 
395
 
 
396
    def bookmarkPage(self, key,
 
397
                      fitType="Fit",
 
398
                      left=None,
 
399
                      top=None,
 
400
                      bottom=None,
 
401
                      right=None,
 
402
                      zoom=None
 
403
                      ):
 
404
        """
 
405
        This creates a bookmark to the current page which can
 
406
        be referred to with the given key elsewhere.
 
407
 
 
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
 
412
        choice of 'fitType'.
 
413
 
 
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.
 
419
 
 
420
        /Fit - entire page fits in window
 
421
 
 
422
        /FitH top - top coord at top of window, width scaled
 
423
                    to fit.
 
424
 
 
425
        /FitV left - left coord at left of window, height
 
426
                     scaled to fit
 
427
 
 
428
        /FitR left bottom right top - scale window to fit
 
429
                                  the specified rectangle
 
430
 
 
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()
 
436
 
 
437
        #None = "null" for PDF
 
438
        if left is None:
 
439
            left = "null"
 
440
        if top is None:
 
441
            top = "null"
 
442
        if bottom is None:
 
443
            bottom = "null"
 
444
        if right is None:
 
445
            right = "null"
 
446
        if zoom is None:
 
447
            zoom = "null"
 
448
 
 
449
        if fitType == "XYZ":
 
450
            dest.xyz(left,top,zoom)
 
451
        elif fitType == "Fit":
 
452
            dest.fit()
 
453
        elif fitType == "FitH":
 
454
            dest.fith(top)
 
455
        elif fitType == "FitV":
 
456
            dest.fitv(left)
 
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":
 
461
            dest.fitb()
 
462
        elif fitType == "FitBH":
 
463
            dest.fitbh(top)
 
464
        elif fitType == "FitBV":
 
465
            dest.fitbv(left)
 
466
        else:
 
467
            raise "Unknown Fit type %s" % (fitType,)
 
468
 
 
469
        dest.setPage(pageref)
 
470
        return dest
 
471
 
 
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
 
479
           the page."""
 
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)
 
482
 
 
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)
 
487
 
 
488
    #def _inPage0(self):  disallowed!
 
489
    #    """declare a page, enable page features"""
 
490
    #    self._doc.inPage()
 
491
 
 
492
    #def _inForm0(self):
 
493
    #    "deprecated in favore of beginForm...endForm"
 
494
    #    self._doc.inForm()
 
495
 
 
496
    def doForm(self, name):
 
497
        """use a form XObj in current operation stream.
 
498
 
 
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
 
503
        state."""
 
504
        self._code.append("/%s Do" % self._doc.getXObjectName(name))
 
505
        self._formsinuse.append(name)
 
506
 
 
507
    def hasForm(self, name):
 
508
        """Query whether form XObj really exists yet."""
 
509
        return self._doc.hasForm(name)
 
510
 
 
511
 
 
512
        ######################################################
 
513
        #
 
514
        #   Image routines
 
515
        #
 
516
        ######################################################
 
517
 
 
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."""
 
523
 
 
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)
 
529
 
 
530
    def drawImage(self, image, x, y, width=None, height=None, mask=None):
 
531
        """Draws the image (ImageReader object or filename) as specified.
 
532
 
 
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.
 
536
 
 
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)
 
542
 
 
543
        The method returns the width and height of the underlying image since
 
544
        this is often useful for layout algorithms.
 
545
 
 
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
 
551
        whether to reuse it.
 
552
 
 
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
 
556
 
 
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(''):
 
560
            #filename, use it
 
561
            name = _digester('%s%s' % (image, mask))
 
562
        else:
 
563
            rawdata = image.getRGBData()
 
564
            name = _digester(rawdata+str(mask))
 
565
 
 
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)
 
570
        if not imgObj:
 
571
            #first time seen, create and register the PDFImageXobject
 
572
            imgObj = pdfdoc.PDFImageXObject(name, image, mask=mask)
 
573
            imgObj.name = name
 
574
            self._setXObjects(imgObj)
 
575
            self._doc.Reference(imgObj, regName)
 
576
            self._doc.addForm(name, imgObj)
 
577
 
 
578
        # ensure we have a size, as PDF will make it 1x1 pixel otherwise!
 
579
        if width is None:
 
580
            width = imgObj.width
 
581
        if height is None:
 
582
            height = imgObj.height
 
583
 
 
584
        # scale and draw
 
585
        self.saveState()
 
586
        self.translate(x, y)
 
587
        self.scale(width, height)
 
588
        self._code.append("/%s Do" % regName)
 
589
        self.restoreState()
 
590
 
 
591
        # track what's been used on this page
 
592
        self._formsinuse.append(name)
 
593
 
 
594
        return (imgObj.width, imgObj.height)
 
595
 
 
596
    def _restartAccumulators(self):
 
597
        if self._codeStack:
 
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
 
602
        else:
 
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
 
609
 
 
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
 
619
 
 
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()
 
629
        if self._code:
 
630
            # save the code that is not in the formf
 
631
            self._pushAccumulators()
 
632
            #self._codeStack.append(self._code)
 
633
            #self._code = []
 
634
        self._formData = (name, lowerx, lowery, upperx, uppery)
 
635
        self._doc.inForm()
 
636
        #self._inForm0()
 
637
 
 
638
    def endForm(self):
 
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()
 
658
 
 
659
 
 
660
    def addPostScriptCommand(self, command, position=1):
 
661
        """Embed literal Postscript in the document.
 
662
 
 
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 :-)
 
667
 
 
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.
 
676
        """
 
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)
 
681
        if not psObj:
 
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)
 
687
        if position == 0:
 
688
            self._psCommandsBeforePage.append("/%s Do" % regName)
 
689
        elif position==1:
 
690
            self._code.append("/%s Do" % regName)
 
691
        else:
 
692
            self._psCommandsAfterPage.append("/%s Do" % regName)
 
693
 
 
694
        self._formsinuse.append(rawName)
 
695
 
 
696
    def textAnnotation0(self, contents, Rect=None, addtopage=1, name=None, **kw):
 
697
        """Experimental.
 
698
        """
 
699
        if not Rect:
 
700
            (w,h) = self._pagesize# default to whole page (?)
 
701
            Rect = (0,0,w,h)
 
702
        annotation = apply(pdfdoc.TextAnnotation, (Rect, contents), kw)
 
703
        self._addAnnotation(annotation, name, addtopage)
 
704
 
 
705
    def inkAnnotation0(self, contents, InkList=None, Rect=None, addtopage=1, name=None, **kw):
 
706
        "Experimental"
 
707
        (w,h) = self._pagesize
 
708
        if not Rect:
 
709
            Rect = (0,0,w,h)
 
710
        if not InkList:
 
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)
 
714
 
 
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
 
719
           by the destination.
 
720
 
 
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).
 
727
 
 
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
 
732
        if not Rect:
 
733
            Rect = (0,0,w,h)
 
734
        kw["Rect"] = Rect
 
735
        kw["Contents"] = contents
 
736
        kw["Destination"] = destination
 
737
        annotation = apply(pdfdoc.LinkAnnotation, (), kw)
 
738
        self._addAnnotation(annotation, name, addtopage)
 
739
 
 
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
 
743
        """
 
744
        if Rect is None:
 
745
            pass # do whatever linkAbsolute does
 
746
        else:
 
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)
 
759
 
 
760
    def linkURL(self, url, rect, relative=0, thickness=0, color=None, dashArray=None):
 
761
        """Create a rectangular URL 'hotspot' in the given rectangle.
 
762
 
 
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!
 
773
        if relative:
 
774
            #adjust coordinates
 
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)
 
786
 
 
787
        ann = PDFDictionary()
 
788
        ann["Type"] = PDFName("Annot")
 
789
        ann["Subtype"] = PDFName("Link")
 
790
        ann["Rect"] = PDFArray(rect) # the whole page for testing
 
791
 
 
792
        # the action is a separate dictionary
 
793
        A = PDFDictionary()
 
794
        A["Type"] = PDFName("Action") # not needed?
 
795
        A["S"] = PDFName("URI")
 
796
        A["URI"] = PDFString(url)
 
797
        ann["A"] = A
 
798
 
 
799
        # now for formatting stuff.
 
800
        if color:
 
801
            ann["C"] = PDFArray([color.red, color.green, color.blue])
 
802
        border = [0,0,0]
 
803
        if thickness:
 
804
            border[2] = thickness
 
805
        if dashArray:
 
806
            border.append(PDFArray(dashArray))
 
807
        ann["Border"] = PDFArray(border)
 
808
 
 
809
        self._addAnnotation(ann)
 
810
 
 
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)
 
815
        if addtopage:
 
816
            self._annotatePage(name)
 
817
 
 
818
    def _annotatePage(self, name):
 
819
        ref = self._doc.refAnnotation(name)
 
820
        self._annotationrefs.append(ref)
 
821
 
 
822
    def getPageNumber(self):
 
823
        "get the page number for the current page being generated."
 
824
        return self._pageNumber
 
825
 
 
826
    def save(self):
 
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)
 
832
 
 
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)
 
839
 
 
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()
 
845
 
 
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
 
850
 
 
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
 
855
        if escaped==0:
 
856
            s = self._escape(s) # convert to string for safety
 
857
        self._code.append(s)
 
858
 
 
859
        ######################################################################
 
860
        #
 
861
        #      coordinate transformations
 
862
        #
 
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:
 
867
              canv.saveState()
 
868
              canv.resetTransforms()
 
869
              ...draw some stuff in default space coords...
 
870
              canv.restoreState() # go back!
 
871
        """
 
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
 
875
        resulta = selfd/det
 
876
        resultc = -selfc/det
 
877
        resulte = (selfc*selff - selfd*selfe)/det
 
878
        resultd = selfa/det
 
879
        resultb = -selfb/det
 
880
        resultf = (selfe*selfb - selff*selfa)/det
 
881
        self.transform(resulta, resultb, resultc, resultd, resulte, resultf)
 
882
 
 
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?"""
 
887
        if ENABLE_TRACKING:
 
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)
 
897
        else:
 
898
            self._code.append('%s cm' % fp_str(a,b,c,d,e,f))
 
899
        ### debug
 
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)
 
907
 
 
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
 
913
        xp = a*x + c*y + e
 
914
        yp = b*x + d*y + f
 
915
        return (xp, yp)
 
916
 
 
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)
 
921
 
 
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)
 
927
 
 
928
    def rotate(self, theta):
 
929
        """Canvas.rotate(theta)
 
930
 
 
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)
 
935
 
 
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)
 
940
 
 
941
        ######################################################################
 
942
        #
 
943
        #      graphics state management
 
944
        #
 
945
        ######################################################################
 
946
 
 
947
    def saveState(self):
 
948
        """Save the current graphics state to be restored later by restoreState.
 
949
 
 
950
        For example:
 
951
            canvas.setFont("Helvetica", 20)
 
952
            canvas.saveState()
 
953
            ...
 
954
            canvas.setFont("Courier", 9)
 
955
            ...
 
956
            canvas.restoreState()
 
957
            # if the save/restore pairs match then font is Helvetica 20 again.
 
958
        """
 
959
        self.push_state_stack()
 
960
        self._code.append('q')
 
961
 
 
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()
 
966
 
 
967
        ###############################################################
 
968
        #
 
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.
 
973
        #
 
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.
 
980
        #
 
981
        #   In the case of lines, we provide a 'plural' to unroll
 
982
        #   the inner loop; it is useful for drawing big grids
 
983
        ################################################################
 
984
 
 
985
        #--------first the line drawing methods-----------------------
 
986
 
 
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)))
 
991
 
 
992
    def lines(self, linelist):
 
993
        """Like line(), permits many lines to be drawn in one call.
 
994
           for example for the figure
 
995
               |
 
996
             -- --
 
997
               |
 
998
 
 
999
             crosshairs = [(20,0,20,10), (20,30,20,40), (0,20,10,20), (30,20,40,20)]
 
1000
             canvas.lines(crosshairs)
 
1001
        """
 
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')
 
1006
 
 
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"
 
1012
        lines = []
 
1013
        y0, y1 = ylist[0], ylist[-1]
 
1014
        x0, x1 = xlist[0], xlist[-1]
 
1015
        for x in xlist:
 
1016
            lines.append((x,y0,x,y1))
 
1017
        for y in ylist:
 
1018
            lines.append((x0,y,x1,y))
 
1019
        self.lines(lines)
 
1020
 
 
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))
 
1025
                          )
 
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.
 
1031
 
 
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.
 
1035
 
 
1036
        The algorithm is an elliptical generalization of the formulae in
 
1037
        Jim Fitzsimmon's TeX tutorial <URL: http://www.tinaja.com/bezarc1.pdf>."""
 
1038
 
 
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:]))
 
1044
        # stroke
 
1045
        self._code.append('S')
 
1046
 
 
1047
        #--------now the shape drawing methods-----------------------
 
1048
 
 
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])
 
1053
 
 
1054
    def ellipse(self, x1, y1, x2, y2, stroke=1, fill=0):
 
1055
        """Draw an ellipse defined by an enclosing rectangle.
 
1056
 
 
1057
        Note that (x1,y1) and (x2,y2) are the corner points of
 
1058
        the enclosing rectangle.
 
1059
 
 
1060
        Uses bezierArc, which conveniently handles 360 degrees.
 
1061
        Special thanks to Robert Kern."""
 
1062
 
 
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:]))
 
1068
        #finish
 
1069
        self._code.append(PATH_OPS[stroke, fill, self._fillMode])
 
1070
 
 
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!"""
 
1074
 
 
1075
        x_cen  = (x1+x2)/2.
 
1076
        y_cen  = (y1+y2)/2.
 
1077
        pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
 
1078
 
 
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:]))
 
1084
        # finish the wedge
 
1085
        self._code.append('%s l ' % fp_str(x_cen, y_cen))
 
1086
        # final operator
 
1087
        self._code.append(PATH_OPS[stroke, fill, self._fillMode])
 
1088
 
 
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)"""
 
1091
 
 
1092
        x1 = x_cen - r
 
1093
        x2 = x_cen + r
 
1094
        y1 = y_cen - r
 
1095
        y2 = y_cen + r
 
1096
        self.ellipse(x1, y1, x2, y2, stroke, fill)
 
1097
 
 
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!
 
1104
        t = 0.4472 * radius
 
1105
 
 
1106
        x0 = x
 
1107
        x1 = x0 + t
 
1108
        x2 = x0 + radius
 
1109
        x3 = x0 + width - radius
 
1110
        x4 = x0 + width - t
 
1111
        x5 = x0 + width
 
1112
 
 
1113
        y0 = y
 
1114
        y1 = y0 + t
 
1115
        y2 = y0 + radius
 
1116
        y3 = y0 + height - radius
 
1117
        y4 = y0 + height - t
 
1118
        y5 = y0 + height
 
1119
 
 
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
 
1124
 
 
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
 
1128
 
 
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
 
1132
 
 
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
 
1136
 
 
1137
        self._code.append('h')  #close off, although it should be where it started anyway
 
1138
 
 
1139
        self._code.append(PATH_OPS[stroke, fill, self._fillMode])
 
1140
 
 
1141
        ##################################################
 
1142
        #
 
1143
        #  Text methods
 
1144
        #
 
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
        ##################################################
 
1150
 
 
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))
 
1157
 
 
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))
 
1164
 
 
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)
 
1169
        t.textLine(text)
 
1170
        self.drawText(t)
 
1171
 
 
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)
 
1176
        t.textLine(text)
 
1177
        self.drawText(t)
 
1178
 
 
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)
 
1183
        t.textLine(text)
 
1184
        self.drawText(t)
 
1185
 
 
1186
    def drawAlignedString(self, x, y, text, pivotChar="."):
 
1187
        """Draws a string aligned on the first '.' (or other pivot character).
 
1188
 
 
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)
 
1192
        leftText = parts[0]
 
1193
        self.drawRightString(x-0.5*pivW, y, leftText)
 
1194
        if len(parts) > 1:
 
1195
            rightText = pivotChar + parts[1]
 
1196
            self.drawString(x-0.5*pivW, y, rightText)
 
1197
 
 
1198
    def getAvailableFonts(self):
 
1199
        """Returns the list of PostScript font names available.
 
1200
 
 
1201
        Standard set now, but may grow in future with font embedding."""
 
1202
        fontnames = self._doc.getAvailableFonts()
 
1203
        fontnames.sort()
 
1204
        return fontnames
 
1205
 
 
1206
    def addFont(self, fontObj):
 
1207
        "add a new font for subsequent use."
 
1208
        self._doc.addFont(fontObj)
 
1209
 
 
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])
 
1215
 
 
1216
    def listLoadedFonts0(self):
 
1217
        "Convenience function to list all loaded fonts"
 
1218
        names = pdfmetrics.widths.keys()
 
1219
        names.sort()
 
1220
        return names
 
1221
 
 
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
 
1229
        if leading is None:
 
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)))
 
1237
 
 
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)
 
1245
 
 
1246
    # basic graphics modes
 
1247
 
 
1248
    def setLineWidth(self, width):
 
1249
        self._lineWidth = width
 
1250
        self._code.append('%s w' % fp_str(width))
 
1251
 
 
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)
 
1257
 
 
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)
 
1263
 
 
1264
    def setMiterLimit(self, limit):
 
1265
        self._miterLimit = limit
 
1266
        self._code.append('%s M' % fp_str(limit))
 
1267
 
 
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))
 
1276
 
 
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))
 
1282
 
 
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))
 
1288
 
 
1289
    def setFillColor(self, aColor):
 
1290
        """Takes a color object, allowing colors to be referred to by name"""
 
1291
        if isinstance(aColor, CMYKColor):
 
1292
            d = aColor.density
 
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:
 
1301
            l = len(aColor)
 
1302
            if l==3:
 
1303
                self._fillColorRGB = aColor
 
1304
                self._code.append('%s rg' % fp_str(aColor) )
 
1305
            elif l==4:
 
1306
                self.setFillColorCMYK(aColor[0], aColor[1], aColor[2], aColor[3])
 
1307
            else:
 
1308
                raise 'Unknown color', str(aColor)
 
1309
        elif type(aColor) is StringType:
 
1310
            self.setFillColor(toColor(aColor))
 
1311
        else:
 
1312
            raise 'Unknown color', str(aColor)
 
1313
 
 
1314
    def setStrokeColor(self, aColor):
 
1315
        """Takes a color object, allowing colors to be referred to by name"""
 
1316
        if isinstance(aColor, CMYKColor):
 
1317
            d = aColor.density
 
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:
 
1327
            l = len(aColor)
 
1328
            if l==3:
 
1329
                self._strokeColorRGB = aColor
 
1330
                self._code.append('%s RG' % fp_str(aColor) )
 
1331
            elif l==4:
 
1332
                self.setStrokeColorCMYK(aColor[0], aColor[1], aColor[2], aColor[3])
 
1333
            else:
 
1334
                raise 'Unknown color', str(aColor)
 
1335
        elif type(aColor) is StringType:
 
1336
            self.setStrokeColor(toColor(aColor))
 
1337
        else:
 
1338
            raise 'Unknown color', str(aColor)
 
1339
 
 
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))
 
1344
 
 
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))
 
1349
 
 
1350
    # path stuff - the separate path object builds it
 
1351
 
 
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()
 
1357
 
 
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])
 
1364
 
 
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])
 
1374
 
 
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)
 
1379
 
 
1380
    def drawText(self, aTextObject):
 
1381
        """Draws a text object"""
 
1382
        self._code.append(str(aTextObject.getCode()))
 
1383
 
 
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()
 
1390
        is next called."""
 
1391
        if pageCompression is None: pageCompression = rl_config.pageCompression
 
1392
        if pageCompression and not zlib:
 
1393
            self._pageCompression = 0
 
1394
        else:
 
1395
            self._pageCompression = pageCompression
 
1396
        self._doc.setCompression(self._pageCompression)
 
1397
 
 
1398
    def setPageDuration(self, duration=None):
 
1399
        """Allows hands-off animation of presentations :-)
 
1400
 
 
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."""
 
1407
 
 
1408
        self._pageDuration = duration
 
1409
 
 
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)
 
1420
 
 
1421
        This table says which ones take which arguments:
 
1422
 
 
1423
        PageTransitionEffects = {
 
1424
            'Split': [direction_arg, motion_arg],
 
1425
            'Blinds': [dimension_arg],
 
1426
            'Box': [motion_arg],
 
1427
            'Wipe' : [direction_arg],
 
1428
            'Dissolve' : [],
 
1429
            'Glitter':[direction_arg]
 
1430
            }
 
1431
        Have fun!
 
1432
        """
 
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 = {}
 
1439
        if not effectname:
 
1440
            return
 
1441
 
 
1442
        #first check each optional argument has an allowed value
 
1443
        if direction in [0,90,180,270]:
 
1444
            direction_arg = ('Di', '/%d' % direction)
 
1445
        else:
 
1446
            raise 'PDFError', ' directions allowed are 0,90,180,270'
 
1447
 
 
1448
        if dimension in ['H', 'V']:
 
1449
            dimension_arg = ('Dm', '/' + dimension)
 
1450
        else:
 
1451
            raise'PDFError','dimension values allowed are H and V'
 
1452
 
 
1453
        if motion in ['I','O']:
 
1454
            motion_arg = ('M', '/' + motion)
 
1455
        else:
 
1456
            raise'PDFError','motion values allowed are I and O'
 
1457
 
 
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],
 
1464
            'Dissolve' : [],
 
1465
            'Glitter':[direction_arg]
 
1466
            }
 
1467
 
 
1468
        try:
 
1469
            args = PageTransitionEffects[effectname]
 
1470
        except KeyError:
 
1471
            raise 'PDFError', 'Unknown Effect Name "%s"' % effectname
 
1472
 
 
1473
        # now build the dictionary
 
1474
        transDict = {}
 
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
 
1481
 
 
1482
if _instanceEscapePDF:
 
1483
    import new
 
1484
    Canvas._escape = new.instancemethod(_instanceEscapePDF,None,Canvas)
 
1485
 
 
1486
if __name__ == '__main__':
 
1487
    print 'For test scripts, look in reportlab/test'