author | rgbecker |
Wed, 03 Sep 2008 17:22:41 +0000 | |
changeset 2967 | ea62529bd1df |
parent 2966 | c9df63ccabdf |
child 3032 | 22224b1b4d24 |
permissions | -rw-r--r-- |
1777 | 1 |
# a Pythonesque Canvas v0.8 |
1738 | 2 |
# Author : Jerome Alet - <alet@librelogiciel.com> |
3 |
# License : ReportLab's license |
|
4 |
# |
|
2966 | 5 |
# $Id$ |
1777 | 6 |
# |
1738 | 7 |
__doc__ = """pycanvas.Canvas : a Canvas class which can also output Python source code. |
8 |
||
1771 | 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. |
|
1742 | 13 |
|
1771 | 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". |
|
1738 | 17 |
|
1771 | 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. |
|
1738 | 27 |
|
2967
ea62529bd1df
reportlab-2.2: first stage changes in on the trunk
rgbecker
parents:
2966
diff
changeset
|
28 |
the tests/test_pdfgen_pycanvas.py program is the test suite |
1771 | 29 |
for pycanvas, you can do the following to run it : |
1738 | 30 |
|
1740 | 31 |
First set verbose=1 in reportlab/rl_config.py |
1771 | 32 |
|
1740 | 33 |
then from the command interpreter : |
1771 | 34 |
|
2967
ea62529bd1df
reportlab-2.2: first stage changes in on the trunk
rgbecker
parents:
2966
diff
changeset
|
35 |
$ cd tests |
1738 | 36 |
$ python test_pdfgen_pycanvas.py >n1.py |
1771 | 37 |
|
1738 | 38 |
this will produce both n1.py and test_pdfgen_pycanvas.pdf |
1771 | 39 |
|
1738 | 40 |
then : |
1771 | 41 |
|
1738 | 42 |
$ python n1.py n1.pdf >n2.py |
43 |
$ python n2.py n2.pdf >n3.py |
|
44 |
$ ... |
|
1771 | 45 |
|
1738 | 46 |
n1.py, n2.py, n3.py and so on will be identical files. |
1771 | 47 |
they eventually may end being a bit different because of |
48 |
rounding problems, mostly in the comments, but this |
|
1742 | 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) |
|
1771 | 51 |
|
1738 | 52 |
n1.pdf, n2.pdf, n3.pdf and so on will be PDF files |
53 |
similar to test_pdfgen_pycanvas.pdf. |
|
1771 | 54 |
|
1738 | 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 : |
|
57 |
||
58 |
import n1 |
|
1742 | 59 |
pythonsource = n1.doIt("myfile.pdf", regenerate=1) |
1771 | 60 |
|
1742 | 61 |
Or if you don't need the python source code and want a faster result : |
62 |
||
63 |
import n1 |
|
64 |
n1.doIt("myfile.pdf") |
|
1771 | 65 |
|
1742 | 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. : |
|
68 |
||
1771 | 69 |
python n1.py |
70 |
||
1742 | 71 |
will print the python source code equivalent to n1.py |
72 |
||
1738 | 73 |
Why would you want to use such a beast ? |
1771 | 74 |
|
75 |
- To linearize (serialize?) a program : optimizing some complex |
|
76 |
parts for example. |
|
77 |
||
1740 | 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 |
|
83 |
application. |
|
1771 | 84 |
|
1738 | 85 |
- To create standalone scripts : say your program uses a high level |
1771 | 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 |
|
1740 | 89 |
have Oracle). |
1771 | 90 |
|
1740 | 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 |
|
93 |
distribute. |
|
1771 | 94 |
|
1738 | 95 |
- ... Insert your own ideas here ... |
1771 | 96 |
|
1738 | 97 |
- For fun because you can do it ! |
98 |
""" |
|
99 |
||
100 |
import cStringIO |
|
101 |
from reportlab.pdfgen import canvas |
|
102 |
from reportlab.pdfgen import pathobject |
|
103 |
from reportlab.pdfgen import textobject |
|
104 |
||
1740 | 105 |
PyHeader = '''#! /usr/bin/env python |
1738 | 106 |
|
1742 | 107 |
# |
108 |
# This code was entirely generated by ReportLab (http://www.reportlab.com) |
|
109 |
# |
|
110 |
||
1738 | 111 |
import sys |
112 |
from reportlab.pdfgen import pathobject |
|
113 |
from reportlab.pdfgen import textobject |
|
114 |
from reportlab.lib.colors import Color |
|
115 |
||
1742 | 116 |
def doIt(file, regenerate=0) : |
117 |
"""Generates a PDF document, save it into file. |
|
1771 | 118 |
|
1740 | 119 |
file : either a filename or a file-like object. |
1771 | 120 |
|
1742 | 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 |
|
124 |
much faster. |
|
125 |
""" |
|
126 |
if regenerate : |
|
127 |
from reportlab.pdfgen.pycanvas import Canvas |
|
128 |
else : |
|
129 |
from reportlab.pdfgen.canvas import Canvas |
|
130 |
''' |
|
1738 | 131 |
|
1740 | 132 |
PyFooter = ''' |
1742 | 133 |
# if we want the equivalent Python source code, then send it back |
134 |
if regenerate : |
|
135 |
return str(c) |
|
1738 | 136 |
|
137 |
if __name__ == "__main__" : |
|
138 |
if len(sys.argv) != 2 : |
|
1740 | 139 |
# second argument must be the name of the PDF file to create |
1738 | 140 |
sys.stderr.write("%s needs one and only one argument\\n" % sys.argv[0]) |
141 |
sys.exit(-1) |
|
142 |
else : |
|
1740 | 143 |
# we've got a filename, we can proceed. |
1771 | 144 |
print doIt(sys.argv[1], regenerate=1) |
1742 | 145 |
sys.exit(0)''' |
1771 | 146 |
|
1738 | 147 |
def buildargs(*args, **kwargs) : |
1742 | 148 |
"""Constructs a printable list of arguments suitable for use in source function calls.""" |
1738 | 149 |
arguments = "" |
150 |
for arg in args : |
|
1746 | 151 |
arguments = arguments + ("%s, " % repr(arg)) |
1738 | 152 |
for (kw, val) in kwargs.items() : |
1746 | 153 |
arguments = arguments+ ("%s=%s, " % (kw, repr(val))) |
1738 | 154 |
if arguments[-2:] == ", " : |
155 |
arguments = arguments[:-2] |
|
1771 | 156 |
return arguments |
1738 | 157 |
|
1739 | 158 |
class PDFAction : |
1742 | 159 |
"""Base class to fake method calls or attributes on PDF objects (Canvas, PDFPathObject, PDFTextObject).""" |
1739 | 160 |
def __init__(self, parent, action) : |
1742 | 161 |
"""Saves a pointer to the parent object, and the method name.""" |
1739 | 162 |
self._parent = parent |
163 |
self._action = action |
|
1771 | 164 |
|
1739 | 165 |
def __getattr__(self, name) : |
1742 | 166 |
"""Probably a method call on an attribute, returns the real one.""" |
1739 | 167 |
return getattr(getattr(self._parent._object, self._action), name) |
1771 | 168 |
|
1739 | 169 |
def __call__(self, *args, **kwargs) : |
1742 | 170 |
"""The fake method is called, print it then call the real one.""" |
171 |
if not self._parent._parent._in : |
|
172 |
self._precomment() |
|
1739 | 173 |
self._parent._parent._PyWrite(" %s.%s(%s)" % (self._parent._name, self._action, apply(buildargs, args, kwargs))) |
1771 | 174 |
self._postcomment() |
175 |
self._parent._parent._in = self._parent._parent._in + 1 |
|
1739 | 176 |
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
|
177 |
self._parent._parent._in = self._parent._parent._in - 1 |
1739 | 178 |
return retcode |
1771 | 179 |
|
1777 | 180 |
def __hash__(self) : |
181 |
return hash(getattr(self._parent._object, self._action)) |
|
1771 | 182 |
|
1756
ad131d58acab
Applied Jerome Alet's patch (submitted on Mon 07/10/2002).
johnprecedo
parents:
1746
diff
changeset
|
183 |
def __coerce__(self, other) : |
1777 | 184 |
"""Needed.""" |
1756
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) |
1771 | 186 |
|
187 |
def _precomment(self) : |
|
1742 | 188 |
"""To be overriden.""" |
189 |
pass |
|
1771 | 190 |
|
1742 | 191 |
def _postcomment(self) : |
192 |
"""To be overriden.""" |
|
193 |
pass |
|
1771 | 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 |
|
1771 | 202 |
|
1739 | 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) |
1771 | 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 |
1771 | 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 |
|
1771 | 219 |
|
1739 | 220 |
class Canvas : |
1771 | 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" |
|
1771 | 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" |
|
1771 | 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) |
|
1771 | 240 |
elif self._action == "saveState" : |
1742 | 241 |
state = {} |
242 |
d = self._parent._object.__dict__ |
|
243 |
for name in self._parent._object.STATE_ATTRIBUTES: |
|
1771 | 244 |
state[name] = d[name] |
1742 | 245 |
self._parent._PyWrite("\n # Saves context level %i %s" % (self._parent._contextlevel, state)) |
1746 | 246 |
self._parent._contextlevel = self._parent._contextlevel + 1 |
1771 | 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])) |
1771 | 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) |
1771 | 253 |
elif self._action == "endForm" : |
1742 | 254 |
self._parent._PyWrite("\n # Ends form %i" % self._parent._formnumber) |
1771 | 255 |
elif self._action == "save" : |
1742 | 256 |
self._parent._PyWrite("\n # Saves the PDF document to disk") |
1771 | 257 |
|
1742 | 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) |
1771 | 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"] |
|
1771 | 280 |
except KeyError : |
1738 | 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 |
|
1771 | 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) |
|
1771 | 301 |
else : |
1738 | 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__': |
|
2967
ea62529bd1df
reportlab-2.2: first stage changes in on the trunk
rgbecker
parents:
2966
diff
changeset
|
309 |
print 'For test scripts, look in tests' |