1
# a Pythonesque Canvas v0.8
2
# Author : Jerome Alet - <alet@librelogiciel.com>
3
# License : ReportLab's license
7
__doc__ = """pycanvas.Canvas : a Canvas class which can also output Python source code.
9
pycanvas.Canvas class works exactly like canvas.Canvas, but you can
10
call str() on pycanvas.Canvas instances. Doing so will return the
11
Python source code equivalent to your own program, which would, when
12
run, produce the same PDF document as your original program.
14
Generated Python source code defines a doIt() function which accepts
15
a filename or file-like object as its first parameter, and an
16
optional boolean parameter named "regenerate".
18
The doIt() function will generate a PDF document and save it in the
19
file you specified in this argument. If the regenerate parameter is
20
set then it will also return an automatically generated equivalent
21
Python source code as a string of text, which you can run again to
22
produce the very same PDF document and the Python source code, which
23
you can run again... ad nauseam ! If the regenerate parameter is
24
unset or not used at all (it then defaults to being unset) then None
25
is returned and the doIt() function is much much faster, it is also
26
much faster than the original non-serialized program.
28
the reportlab/test/test_pdfgen_pycanvas.py program is the test suite
29
for pycanvas, you can do the following to run it :
31
First set verbose=1 in reportlab/rl_config.py
33
then from the command interpreter :
36
$ python test_pdfgen_pycanvas.py >n1.py
38
this will produce both n1.py and test_pdfgen_pycanvas.pdf
42
$ python n1.py n1.pdf >n2.py
43
$ python n2.py n2.pdf >n3.py
46
n1.py, n2.py, n3.py and so on will be identical files.
47
they eventually may end being a bit different because of
48
rounding problems, mostly in the comments, but this
49
doesn't matter since the values really are the same
50
(e.g. 0 instead of 0.0, or .53 instead of 0.53)
52
n1.pdf, n2.pdf, n3.pdf and so on will be PDF files
53
similar to test_pdfgen_pycanvas.pdf.
55
Alternatively you can import n1.py (or n3.py, or n16384.py if you prefer)
56
in your own program, and then call its doIt function :
59
pythonsource = n1.doIt("myfile.pdf", regenerate=1)
61
Or if you don't need the python source code and want a faster result :
66
When the generated source code is run directly as an independant program,
67
then the equivalent python source code is printed to stdout, e.g. :
71
will print the python source code equivalent to n1.py
73
Why would you want to use such a beast ?
75
- To linearize (serialize?) a program : optimizing some complex
78
- To debug : reading the generated Python source code may help you or
79
the ReportLab team to diagnose problems. The generated code is now
80
clearly commented and shows nesting levels, page numbers, and so
81
on. You can use the generated script when asking for support : we
82
can see the results you obtain without needing your datas or complete
85
- To create standalone scripts : say your program uses a high level
86
environment to generate its output (databases, RML, etc...), using
87
this class would give you an equivalent program but with complete
88
independance from the high level environment (e.g. if you don't
91
- To contribute some nice looking PDF documents to the ReportLab website
92
without having to send a complete application you don't want to
95
- ... Insert your own ideas here ...
97
- For fun because you can do it !
101
from reportlab.pdfgen import canvas
102
from reportlab.pdfgen import pathobject
103
from reportlab.pdfgen import textobject
105
PyHeader = '''#! /usr/bin/env python
108
# This code was entirely generated by ReportLab (http://www.reportlab.com)
112
from reportlab.pdfgen import pathobject
113
from reportlab.pdfgen import textobject
114
from reportlab.lib.colors import Color
116
def doIt(file, regenerate=0) :
117
"""Generates a PDF document, save it into file.
119
file : either a filename or a file-like object.
121
regenerate : if set then this function returns the Python source
122
code which when run will produce the same result.
123
if unset then this function returns None, and is
127
from reportlab.pdfgen.pycanvas import Canvas
129
from reportlab.pdfgen.canvas import Canvas
133
# if we want the equivalent Python source code, then send it back
137
if __name__ == "__main__" :
138
if len(sys.argv) != 2 :
139
# second argument must be the name of the PDF file to create
140
sys.stderr.write("%s needs one and only one argument\\n" % sys.argv[0])
143
# we've got a filename, we can proceed.
144
print doIt(sys.argv[1], regenerate=1)
147
def buildargs(*args, **kwargs) :
148
"""Constructs a printable list of arguments suitable for use in source function calls."""
151
arguments = arguments + ("%s, " % repr(arg))
152
for (kw, val) in kwargs.items() :
153
arguments = arguments+ ("%s=%s, " % (kw, repr(val)))
154
if arguments[-2:] == ", " :
155
arguments = arguments[:-2]
159
"""Base class to fake method calls or attributes on PDF objects (Canvas, PDFPathObject, PDFTextObject)."""
160
def __init__(self, parent, action) :
161
"""Saves a pointer to the parent object, and the method name."""
162
self._parent = parent
163
self._action = action
165
def __getattr__(self, name) :
166
"""Probably a method call on an attribute, returns the real one."""
167
return getattr(getattr(self._parent._object, self._action), name)
169
def __call__(self, *args, **kwargs) :
170
"""The fake method is called, print it then call the real one."""
171
if not self._parent._parent._in :
173
self._parent._parent._PyWrite(" %s.%s(%s)" % (self._parent._name, self._action, apply(buildargs, args, kwargs)))
175
self._parent._parent._in = self._parent._parent._in + 1
176
retcode = apply(getattr(self._parent._object, self._action), args, kwargs)
177
self._parent._parent._in = self._parent._parent._in - 1
181
return hash(getattr(self._parent._object, self._action))
183
def __coerce__(self, other) :
185
return coerce(getattr(self._parent._object, self._action), other)
187
def _precomment(self) :
188
"""To be overriden."""
191
def _postcomment(self) :
192
"""To be overriden."""
196
"""Base class for PDF objects like PDFPathObject and PDFTextObject."""
198
def __init__(self, parent) :
199
"""Saves a pointer to the parent Canvas."""
200
self._parent = parent
203
def __getattr__(self, name) :
204
"""The user's programs wants to call one of our methods or get an attribute, fake it."""
205
return PDFAction(self, name)
208
"""Returns the name used in the generated source code (e.g. 'p' or 't')."""
211
def __call__(self, *args, **kwargs) :
212
"""Real object initialisation is made here, because now we've got the arguments."""
213
if not self._initdone :
214
self.__class__._number = self.__class__._number + 1
215
methodname = apply(self._postinit, args, kwargs)
216
self._parent._PyWrite("\n # create PDF%sObject number %i\n %s = %s.%s(%s)" % (methodname[5:], self.__class__._number, self._name, self._parent._name, methodname, apply(buildargs, args, kwargs)))
221
"""Our fake Canvas class, which will intercept each and every method or attribute access."""
222
class TextObject(PDFObject) :
224
def _postinit(self, *args, **kwargs) :
225
self._object = apply(textobject.PDFTextObject, (self._parent, ) + args, kwargs)
228
class PathObject(PDFObject) :
230
def _postinit(self, *args, **kwargs) :
231
self._object = apply(pathobject.PDFPathObject, args, kwargs)
234
class Action(PDFAction) :
235
"""Class called for every Canvas method call."""
236
def _precomment(self) :
237
"""Outputs comments before the method call."""
238
if self._action == "showPage" :
239
self._parent._PyWrite("\n # Ends page %i" % self._parent._pagenumber)
240
elif self._action == "saveState" :
242
d = self._parent._object.__dict__
243
for name in self._parent._object.STATE_ATTRIBUTES:
244
state[name] = d[name]
245
self._parent._PyWrite("\n # Saves context level %i %s" % (self._parent._contextlevel, state))
246
self._parent._contextlevel = self._parent._contextlevel + 1
247
elif self._action == "restoreState" :
248
self._parent._contextlevel = self._parent._contextlevel - 1
249
self._parent._PyWrite("\n # Restores context level %i %s" % (self._parent._contextlevel, self._parent._object.state_stack[-1]))
250
elif self._action == "beginForm" :
251
self._parent._formnumber = self._parent._formnumber + 1
252
self._parent._PyWrite("\n # Begins form %i" % self._parent._formnumber)
253
elif self._action == "endForm" :
254
self._parent._PyWrite("\n # Ends form %i" % self._parent._formnumber)
255
elif self._action == "save" :
256
self._parent._PyWrite("\n # Saves the PDF document to disk")
258
def _postcomment(self) :
259
"""Outputs comments after the method call."""
260
if self._action == "showPage" :
261
self._parent._pagenumber = self._parent._pagenumber + 1
262
self._parent._PyWrite("\n # Begins page %i" % self._parent._pagenumber)
263
elif self._action in [ "endForm", "drawPath", "clipPath" ] :
264
self._parent._PyWrite("")
267
def __init__(self, *args, **kwargs) :
268
"""Initialize and begins source code."""
269
self._parent = self # nice trick, isn't it ?
271
self._contextlevel = 0
274
self._footerpresent = 0
275
self._object = apply(canvas.Canvas, args, kwargs)
276
self._pyfile = cStringIO.StringIO()
277
self._PyWrite(PyHeader)
279
del kwargs["filename"]
282
self._PyWrite(" # create the PDF document\n %s = Canvas(file, %s)\n\n # Begins page 1" % (self._name, apply(buildargs, args[1:], kwargs)))
284
def __nonzero__(self) :
285
"""This is needed by platypus' tables."""
289
"""Returns the equivalent Python source code."""
290
if not self._footerpresent :
291
self._PyWrite(PyFooter)
292
self._footerpresent = 1
293
return self._pyfile.getvalue()
295
def __getattr__(self, name) :
296
"""Method or attribute access."""
297
if name == "beginPath" :
298
return self.PathObject(self)
299
elif name == "beginText" :
300
return self.TextObject(self)
302
return self.Action(self, name)
304
def _PyWrite(self, pycode) :
305
"""Outputs the source code with a trailing newline."""
306
self._pyfile.write("%s\n" % pycode)
308
if __name__ == '__main__':
309
print 'For test scripts, look in reportlab/test'