author | johnprecedo |
Fri, 18 Oct 2002 13:48:37 +0000 | |
changeset 1756 | ad131d58acab |
parent 1746 | bc04484b8111 |
child 1771 | 105572a4222f |
permissions | -rw-r--r-- |
1756
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
1 |
# a Pythonesque Canvas v0.7 |
1738 | 2 |
# Author : Jerome Alet - <alet@librelogiciel.com> |
3 |
# License : ReportLab's license |
|
4 |
# |
|
5 |
||
6 |
__doc__ = """pycanvas.Canvas : a Canvas class which can also output Python source code. |
|
7 |
||
1742 | 8 |
pycanvas.Canvas class works exactly like canvas.Canvas, but you can |
9 |
call str() on pycanvas.Canvas instances. Doing so will return the |
|
10 |
Python source code equivalent to your own program, which would, when |
|
11 |
run, produce the same PDF document as your original program. |
|
12 |
||
13 |
Generated Python source code defines a doIt() function which accepts |
|
14 |
a filename or file-like object as its first parameter, and an |
|
15 |
optional boolean parameter named "regenerate". |
|
1738 | 16 |
|
1742 | 17 |
The doIt() function will generate a PDF document and save it in the |
18 |
file you specified in this argument. If the regenerate parameter is |
|
19 |
set then it will also return an automatically generated equivalent |
|
20 |
Python source code as a string of text, which you can run again to |
|
21 |
produce the very same PDF document and the Python source code, which |
|
22 |
you can run again... ad nauseam ! If the regenerate parameter is |
|
23 |
unset or not used at all (it then defaults to being unset) then None |
|
24 |
is returned and the doIt() function is much much faster, it is also |
|
25 |
much faster than the original non-serialized program. |
|
1738 | 26 |
|
1742 | 27 |
the reportlab/test/test_pdfgen_pycanvas.py program is the test suite |
28 |
for pycanvas, you can do the following to run it : |
|
1738 | 29 |
|
1740 | 30 |
First set verbose=1 in reportlab/rl_config.py |
31 |
||
32 |
then from the command interpreter : |
|
33 |
||
1738 | 34 |
$ cd reportlab/test |
35 |
$ python test_pdfgen_pycanvas.py >n1.py |
|
36 |
||
37 |
this will produce both n1.py and test_pdfgen_pycanvas.pdf |
|
38 |
||
39 |
then : |
|
40 |
||
41 |
$ python n1.py n1.pdf >n2.py |
|
42 |
$ python n2.py n2.pdf >n3.py |
|
43 |
$ ... |
|
44 |
||
45 |
n1.py, n2.py, n3.py and so on will be identical files. |
|
1742 | 46 |
they eventually may end being a bit different because of |
47 |
rounding problems, mostly in the comments, but this |
|
48 |
doesn't matter since the values really are the same |
|
49 |
(e.g. 0 instead of 0.0, or .53 instead of 0.53) |
|
1738 | 50 |
|
51 |
n1.pdf, n2.pdf, n3.pdf and so on will be PDF files |
|
52 |
similar to test_pdfgen_pycanvas.pdf. |
|
53 |
||
54 |
Alternatively you can import n1.py (or n3.py, or n16384.py if you prefer) |
|
55 |
in your own program, and then call its doIt function : |
|
56 |
||
57 |
import n1 |
|
1742 | 58 |
pythonsource = n1.doIt("myfile.pdf", regenerate=1) |
59 |
||
60 |
Or if you don't need the python source code and want a faster result : |
|
61 |
||
62 |
import n1 |
|
63 |
n1.doIt("myfile.pdf") |
|
1738 | 64 |
|
1742 | 65 |
When the generated source code is run directly as an independant program, |
66 |
then the equivalent python source code is printed to stdout, e.g. : |
|
67 |
||
68 |
python n1.py |
|
69 |
||
70 |
will print the python source code equivalent to n1.py |
|
71 |
||
1738 | 72 |
Why would you want to use such a beast ? |
73 |
||
1742 | 74 |
- To linearize (serialize?) a program : optimizing some complex |
75 |
parts for example. |
|
1738 | 76 |
|
1740 | 77 |
- To debug : reading the generated Python source code may help you or |
78 |
the ReportLab team to diagnose problems. The generated code is now |
|
79 |
clearly commented and shows nesting levels, page numbers, and so |
|
80 |
on. You can use the generated script when asking for support : we |
|
81 |
can see the results you obtain without needing your datas or complete |
|
82 |
application. |
|
1738 | 83 |
|
84 |
- To create standalone scripts : say your program uses a high level |
|
85 |
environment to generate its output (databases, RML, etc...), using |
|
86 |
this class would give you an equivalent program but with complete |
|
87 |
independance from the high level environment (e.g. if you don't |
|
1740 | 88 |
have Oracle). |
1738 | 89 |
|
1740 | 90 |
- To contribute some nice looking PDF documents to the ReportLab website |
91 |
without having to send a complete application you don't want to |
|
92 |
distribute. |
|
93 |
||
1738 | 94 |
- ... Insert your own ideas here ... |
95 |
||
96 |
- For fun because you can do it ! |
|
97 |
""" |
|
98 |
||
99 |
import cStringIO |
|
100 |
from reportlab.pdfgen import canvas |
|
101 |
from reportlab.pdfgen import pathobject |
|
102 |
from reportlab.pdfgen import textobject |
|
103 |
||
1740 | 104 |
PyHeader = '''#! /usr/bin/env python |
1738 | 105 |
|
1742 | 106 |
# |
107 |
# This code was entirely generated by ReportLab (http://www.reportlab.com) |
|
108 |
# |
|
109 |
||
1738 | 110 |
import sys |
111 |
from reportlab.pdfgen import pathobject |
|
112 |
from reportlab.pdfgen import textobject |
|
113 |
from reportlab.lib.colors import Color |
|
114 |
||
1742 | 115 |
def doIt(file, regenerate=0) : |
116 |
"""Generates a PDF document, save it into file. |
|
1740 | 117 |
|
118 |
file : either a filename or a file-like object. |
|
119 |
||
1742 | 120 |
regenerate : if set then this function returns the Python source |
121 |
code which when run will produce the same result. |
|
122 |
if unset then this function returns None, and is |
|
123 |
much faster. |
|
124 |
""" |
|
125 |
if regenerate : |
|
126 |
from reportlab.pdfgen.pycanvas import Canvas |
|
127 |
else : |
|
128 |
from reportlab.pdfgen.canvas import Canvas |
|
129 |
''' |
|
1738 | 130 |
|
1740 | 131 |
PyFooter = ''' |
1742 | 132 |
# if we want the equivalent Python source code, then send it back |
133 |
if regenerate : |
|
134 |
return str(c) |
|
1738 | 135 |
|
136 |
if __name__ == "__main__" : |
|
137 |
if len(sys.argv) != 2 : |
|
1740 | 138 |
# second argument must be the name of the PDF file to create |
1738 | 139 |
sys.stderr.write("%s needs one and only one argument\\n" % sys.argv[0]) |
140 |
sys.exit(-1) |
|
141 |
else : |
|
1740 | 142 |
# we've got a filename, we can proceed. |
1742 | 143 |
print doIt(sys.argv[1], regenerate=1) |
144 |
sys.exit(0)''' |
|
1738 | 145 |
|
146 |
def buildargs(*args, **kwargs) : |
|
1742 | 147 |
"""Constructs a printable list of arguments suitable for use in source function calls.""" |
1738 | 148 |
arguments = "" |
149 |
for arg in args : |
|
1746 | 150 |
arguments = arguments + ("%s, " % repr(arg)) |
1738 | 151 |
for (kw, val) in kwargs.items() : |
1746 | 152 |
arguments = arguments+ ("%s=%s, " % (kw, repr(val))) |
1738 | 153 |
if arguments[-2:] == ", " : |
154 |
arguments = arguments[:-2] |
|
155 |
return arguments |
|
156 |
||
1739 | 157 |
class PDFAction : |
1742 | 158 |
"""Base class to fake method calls or attributes on PDF objects (Canvas, PDFPathObject, PDFTextObject).""" |
1739 | 159 |
def __init__(self, parent, action) : |
1742 | 160 |
"""Saves a pointer to the parent object, and the method name.""" |
1739 | 161 |
self._parent = parent |
162 |
self._action = action |
|
163 |
||
164 |
def __getattr__(self, name) : |
|
1742 | 165 |
"""Probably a method call on an attribute, returns the real one.""" |
1739 | 166 |
return getattr(getattr(self._parent._object, self._action), name) |
1738 | 167 |
|
1739 | 168 |
def __call__(self, *args, **kwargs) : |
1742 | 169 |
"""The fake method is called, print it then call the real one.""" |
170 |
if not self._parent._parent._in : |
|
171 |
self._precomment() |
|
1739 | 172 |
self._parent._parent._PyWrite(" %s.%s(%s)" % (self._parent._name, self._action, apply(buildargs, args, kwargs))) |
1742 | 173 |
self._postcomment() |
1746 | 174 |
self._parent._parent._in = self._parent._parent._in + 1 |
1739 | 175 |
retcode = apply(getattr(self._parent._object, self._action), args, kwargs) |
1756
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
176 |
self._parent._parent._in = self._parent._parent._in - 1 |
1739 | 177 |
return retcode |
1738 | 178 |
|
1756
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
179 |
def __str__(self) : |
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
180 |
"""Needed for Python 2.1""" |
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
181 |
return str(getattr(self._parent._object, self._action)) |
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
182 |
|
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
183 |
def __coerce__(self, other) : |
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
184 |
"""Needed for Python 2.1""" |
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
185 |
return coerce(getattr(self._parent._object, self._action), other) |
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
186 |
|
1742 | 187 |
def _precomment(self) : |
188 |
"""To be overriden.""" |
|
189 |
pass |
|
190 |
||
191 |
def _postcomment(self) : |
|
192 |
"""To be overriden.""" |
|
193 |
pass |
|
194 |
||
1739 | 195 |
class PDFObject : |
1742 | 196 |
"""Base class for PDF objects like PDFPathObject and PDFTextObject.""" |
1740 | 197 |
_number = 0 |
1739 | 198 |
def __init__(self, parent) : |
1742 | 199 |
"""Saves a pointer to the parent Canvas.""" |
1739 | 200 |
self._parent = parent |
201 |
self._initdone = 0 |
|
202 |
||
203 |
def __getattr__(self, name) : |
|
1742 | 204 |
"""The user's programs wants to call one of our methods or get an attribute, fake it.""" |
1739 | 205 |
return PDFAction(self, name) |
1738 | 206 |
|
1739 | 207 |
def __repr__(self) : |
1742 | 208 |
"""Returns the name used in the generated source code (e.g. 'p' or 't').""" |
1739 | 209 |
return self._name |
1738 | 210 |
|
1739 | 211 |
def __call__(self, *args, **kwargs) : |
1742 | 212 |
"""Real object initialisation is made here, because now we've got the arguments.""" |
1739 | 213 |
if not self._initdone : |
1746 | 214 |
self.__class__._number = self.__class__._number + 1 |
1739 | 215 |
methodname = apply(self._postinit, args, kwargs) |
1740 | 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))) |
1739 | 217 |
self._initdone = 1 |
218 |
return self |
|
219 |
||
220 |
class Canvas : |
|
1742 | 221 |
"""Our fake Canvas class, which will intercept each and every method or attribute access.""" |
1739 | 222 |
class TextObject(PDFObject) : |
223 |
_name = "t" |
|
224 |
def _postinit(self, *args, **kwargs) : |
|
225 |
self._object = apply(textobject.PDFTextObject, (self._parent, ) + args, kwargs) |
|
226 |
return "beginText" |
|
1738 | 227 |
|
1739 | 228 |
class PathObject(PDFObject) : |
229 |
_name = "p" |
|
230 |
def _postinit(self, *args, **kwargs) : |
|
231 |
self._object = apply(pathobject.PDFPathObject, args, kwargs) |
|
232 |
return "beginPath" |
|
1738 | 233 |
|
1739 | 234 |
class Action(PDFAction) : |
1742 | 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" : |
|
241 |
state = {} |
|
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)) |
|
1746 | 246 |
self._parent._contextlevel = self._parent._contextlevel + 1 |
1742 | 247 |
elif self._action == "restoreState" : |
1756
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
248 |
self._parent._contextlevel = self._parent._contextlevel - 1 |
1742 | 249 |
self._parent._PyWrite("\n # Restores context level %i %s" % (self._parent._contextlevel, self._parent._object.state_stack[-1])) |
250 |
elif self._action == "beginForm" : |
|
1746 | 251 |
self._parent._formnumber = self._parent._formnumber + 1 |
1742 | 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") |
|
257 |
||
258 |
def _postcomment(self) : |
|
259 |
"""Outputs comments after the method call.""" |
|
260 |
if self._action == "showPage" : |
|
1746 | 261 |
self._parent._pagenumber = self._parent._pagenumber + 1 |
1742 | 262 |
self._parent._PyWrite("\n # Begins page %i" % self._parent._pagenumber) |
1756
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
263 |
elif self._action in [ "endForm", "drawPath", "clipPath" ] : |
1742 | 264 |
self._parent._PyWrite("") |
1738 | 265 |
|
1739 | 266 |
_name = "c" |
1738 | 267 |
def __init__(self, *args, **kwargs) : |
1742 | 268 |
"""Initialize and begins source code.""" |
269 |
self._parent = self # nice trick, isn't it ? |
|
270 |
self._in = 0 |
|
1740 | 271 |
self._contextlevel = 0 |
272 |
self._pagenumber = 1 |
|
273 |
self._formnumber = 0 |
|
1738 | 274 |
self._footerpresent = 0 |
1739 | 275 |
self._object = apply(canvas.Canvas, args, kwargs) |
1738 | 276 |
self._pyfile = cStringIO.StringIO() |
277 |
self._PyWrite(PyHeader) |
|
278 |
try : |
|
279 |
del kwargs["filename"] |
|
280 |
except KeyError : |
|
281 |
pass |
|
1742 | 282 |
self._PyWrite(" # create the PDF document\n %s = Canvas(file, %s)\n\n # Begins page 1" % (self._name, apply(buildargs, args[1:], kwargs))) |
1738 | 283 |
|
1742 | 284 |
def __nonzero__(self) : |
285 |
"""This is needed by platypus' tables.""" |
|
286 |
return 1 |
|
287 |
||
1738 | 288 |
def __str__(self) : |
1742 | 289 |
"""Returns the equivalent Python source code.""" |
1738 | 290 |
if not self._footerpresent : |
291 |
self._PyWrite(PyFooter) |
|
292 |
self._footerpresent = 1 |
|
293 |
return self._pyfile.getvalue() |
|
294 |
||
295 |
def __getattr__(self, name) : |
|
1742 | 296 |
"""Method or attribute access.""" |
1738 | 297 |
if name == "beginPath" : |
298 |
return self.PathObject(self) |
|
299 |
elif name == "beginText" : |
|
300 |
return self.TextObject(self) |
|
301 |
else : |
|
302 |
return self.Action(self, name) |
|
303 |
||
304 |
def _PyWrite(self, pycode) : |
|
1742 | 305 |
"""Outputs the source code with a trailing newline.""" |
1738 | 306 |
self._pyfile.write("%s\n" % pycode) |
307 |
||
308 |
if __name__ == '__main__': |
|
309 |
print 'For test scripts, look in reportlab/test' |