src/reportlab/pdfgen/pycanvas.py
changeset 2964 32352db0d71e
parent 1777 48870c5f865a
child 2966 c9df63ccabdf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/reportlab/pdfgen/pycanvas.py	Wed Sep 03 16:10:51 2008 +0000
@@ -0,0 +1,309 @@
+# a Pythonesque Canvas v0.8
+# Author : Jerome Alet - <alet@librelogiciel.com>
+# License : ReportLab's license
+#
+# $Id: pycanvas.py,v 1.8 2002/11/06 17:11:15 rgbecker Exp $
+#
+__doc__ = """pycanvas.Canvas : a Canvas class which can also output Python source code.
+
+pycanvas.Canvas class works exactly like canvas.Canvas, but you can
+call str() on pycanvas.Canvas instances. Doing so will return the
+Python source code equivalent to your own program, which would, when
+run, produce the same PDF document as your original program.
+
+Generated Python source code defines a doIt() function which accepts
+a filename or file-like object as its first parameter, and an
+optional boolean parameter named "regenerate".
+
+The doIt() function will generate a PDF document and save it in the
+file you specified in this argument. If the regenerate parameter is
+set then it will also return an automatically generated equivalent
+Python source code as a string of text, which you can run again to
+produce the very same PDF document and the Python source code, which
+you can run again... ad nauseam ! If the regenerate parameter is
+unset or not used at all (it then defaults to being unset) then None
+is returned and the doIt() function is much much faster, it is also
+much faster than the original non-serialized program.
+
+the reportlab/test/test_pdfgen_pycanvas.py program is the test suite
+for pycanvas, you can do the following to run it :
+
+    First set verbose=1 in reportlab/rl_config.py
+
+    then from the command interpreter :
+
+    $ cd reportlab/test
+    $ python test_pdfgen_pycanvas.py >n1.py
+
+    this will produce both n1.py and test_pdfgen_pycanvas.pdf
+
+    then :
+
+    $ python n1.py n1.pdf >n2.py
+    $ python n2.py n2.pdf >n3.py
+    $ ...
+
+    n1.py, n2.py, n3.py and so on will be identical files.
+    they eventually may end being a bit different because of
+    rounding problems, mostly in the comments, but this
+    doesn't matter since the values really are the same
+    (e.g. 0 instead of 0.0, or .53 instead of 0.53)
+
+    n1.pdf, n2.pdf, n3.pdf and so on will be PDF files
+    similar to test_pdfgen_pycanvas.pdf.
+
+Alternatively you can import n1.py (or n3.py, or n16384.py if you prefer)
+in your own program, and then call its doIt function :
+
+    import n1
+    pythonsource = n1.doIt("myfile.pdf", regenerate=1)
+
+Or if you don't need the python source code and want a faster result :
+
+    import n1
+    n1.doIt("myfile.pdf")
+
+When the generated source code is run directly as an independant program,
+then the equivalent python source code is printed to stdout, e.g. :
+
+    python n1.py
+
+    will print the python source code equivalent to n1.py
+
+Why would you want to use such a beast ?
+
+    - To linearize (serialize?) a program : optimizing some complex
+      parts for example.
+
+    - To debug : reading the generated Python source code may help you or
+      the ReportLab team to diagnose problems. The generated code is now
+      clearly commented and shows nesting levels, page numbers, and so
+      on. You can use the generated script when asking for support : we
+      can see the results you obtain without needing your datas or complete
+      application.
+
+    - To create standalone scripts : say your program uses a high level
+      environment to generate its output (databases, RML, etc...), using
+      this class would give you an equivalent program but with complete
+      independance from the high level environment (e.g. if you don't
+      have Oracle).
+
+    - To contribute some nice looking PDF documents to the ReportLab website
+      without having to send a complete application you don't want to
+      distribute.
+
+    - ... Insert your own ideas here ...
+
+    - For fun because you can do it !
+"""
+
+import cStringIO
+from reportlab.pdfgen import canvas
+from reportlab.pdfgen import pathobject
+from reportlab.pdfgen import textobject
+
+PyHeader = '''#! /usr/bin/env python
+
+#
+# This code was entirely generated by ReportLab (http://www.reportlab.com)
+#
+
+import sys
+from reportlab.pdfgen import pathobject
+from reportlab.pdfgen import textobject
+from reportlab.lib.colors import Color
+
+def doIt(file, regenerate=0) :
+    """Generates a PDF document, save it into file.
+
+       file : either a filename or a file-like object.
+
+       regenerate : if set then this function returns the Python source
+                    code which when run will produce the same result.
+                    if unset then this function returns None, and is
+                    much faster.
+    """
+    if regenerate :
+        from reportlab.pdfgen.pycanvas import Canvas
+    else :
+        from reportlab.pdfgen.canvas import Canvas
+'''
+
+PyFooter = '''
+    # if we want the equivalent Python source code, then send it back
+    if regenerate :
+        return str(c)
+
+if __name__ == "__main__" :
+    if len(sys.argv) != 2 :
+        # second argument must be the name of the PDF file to create
+        sys.stderr.write("%s needs one and only one argument\\n" % sys.argv[0])
+        sys.exit(-1)
+    else :
+        # we've got a filename, we can proceed.
+        print doIt(sys.argv[1], regenerate=1)
+        sys.exit(0)'''
+
+def buildargs(*args, **kwargs) :
+    """Constructs a printable list of arguments suitable for use in source function calls."""
+    arguments = ""
+    for arg in args :
+        arguments = arguments + ("%s, " % repr(arg))
+    for (kw, val) in kwargs.items() :
+        arguments = arguments+ ("%s=%s, " % (kw, repr(val)))
+    if arguments[-2:] == ", " :
+        arguments = arguments[:-2]
+    return arguments
+
+class PDFAction :
+    """Base class to fake method calls or attributes on PDF objects (Canvas, PDFPathObject, PDFTextObject)."""
+    def __init__(self, parent, action) :
+        """Saves a pointer to the parent object, and the method name."""
+        self._parent = parent
+        self._action = action
+
+    def __getattr__(self, name) :
+        """Probably a method call on an attribute, returns the real one."""
+        return getattr(getattr(self._parent._object, self._action), name)
+
+    def __call__(self, *args, **kwargs) :
+        """The fake method is called, print it then call the real one."""
+        if not self._parent._parent._in :
+            self._precomment()
+            self._parent._parent._PyWrite("    %s.%s(%s)" % (self._parent._name, self._action, apply(buildargs, args, kwargs)))
+            self._postcomment()
+        self._parent._parent._in = self._parent._parent._in + 1
+        retcode = apply(getattr(self._parent._object, self._action), args, kwargs)
+        self._parent._parent._in = self._parent._parent._in - 1
+        return retcode
+
+    def __hash__(self) :
+        return hash(getattr(self._parent._object, self._action))
+
+    def __coerce__(self, other) :
+        """Needed."""
+        return coerce(getattr(self._parent._object, self._action), other)
+
+    def _precomment(self) :
+        """To be overriden."""
+        pass
+
+    def _postcomment(self) :
+        """To be overriden."""
+        pass
+
+class PDFObject :
+    """Base class for PDF objects like PDFPathObject and PDFTextObject."""
+    _number = 0
+    def __init__(self, parent) :
+        """Saves a pointer to the parent Canvas."""
+        self._parent = parent
+        self._initdone = 0
+
+    def __getattr__(self, name) :
+        """The user's programs wants to call one of our methods or get an attribute, fake it."""
+        return PDFAction(self, name)
+
+    def __repr__(self) :
+        """Returns the name used in the generated source code (e.g. 'p' or 't')."""
+        return self._name
+
+    def __call__(self, *args, **kwargs) :
+        """Real object initialisation is made here, because now we've got the arguments."""
+        if not self._initdone :
+            self.__class__._number = self.__class__._number + 1
+            methodname = apply(self._postinit, args, kwargs)
+            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)))
+            self._initdone = 1
+        return self
+
+class Canvas :
+    """Our fake Canvas class, which will intercept each and every method or attribute access."""
+    class TextObject(PDFObject) :
+        _name = "t"
+        def _postinit(self, *args, **kwargs) :
+            self._object = apply(textobject.PDFTextObject, (self._parent, ) + args, kwargs)
+            return "beginText"
+
+    class PathObject(PDFObject) :
+        _name = "p"
+        def _postinit(self, *args, **kwargs) :
+            self._object = apply(pathobject.PDFPathObject, args, kwargs)
+            return "beginPath"
+
+    class Action(PDFAction) :
+        """Class called for every Canvas method call."""
+        def _precomment(self) :
+            """Outputs comments before the method call."""
+            if self._action == "showPage" :
+                self._parent._PyWrite("\n    # Ends page %i" % self._parent._pagenumber)
+            elif self._action == "saveState" :
+                state = {}
+                d = self._parent._object.__dict__
+                for name in self._parent._object.STATE_ATTRIBUTES:
+                    state[name] = d[name]
+                self._parent._PyWrite("\n    # Saves context level %i %s" % (self._parent._contextlevel, state))
+                self._parent._contextlevel = self._parent._contextlevel + 1
+            elif self._action == "restoreState" :
+                self._parent._contextlevel = self._parent._contextlevel - 1
+                self._parent._PyWrite("\n    # Restores context level %i %s" % (self._parent._contextlevel, self._parent._object.state_stack[-1]))
+            elif self._action == "beginForm" :
+                self._parent._formnumber = self._parent._formnumber + 1
+                self._parent._PyWrite("\n    # Begins form %i" % self._parent._formnumber)
+            elif self._action == "endForm" :
+                self._parent._PyWrite("\n    # Ends form %i" % self._parent._formnumber)
+            elif self._action == "save" :
+                self._parent._PyWrite("\n    # Saves the PDF document to disk")
+
+        def _postcomment(self) :
+            """Outputs comments after the method call."""
+            if self._action == "showPage" :
+                self._parent._pagenumber = self._parent._pagenumber + 1
+                self._parent._PyWrite("\n    # Begins page %i" % self._parent._pagenumber)
+            elif self._action in [ "endForm", "drawPath", "clipPath" ] :
+                self._parent._PyWrite("")
+
+    _name = "c"
+    def __init__(self, *args, **kwargs) :
+        """Initialize and begins source code."""
+        self._parent = self     # nice trick, isn't it ?
+        self._in = 0
+        self._contextlevel = 0
+        self._pagenumber = 1
+        self._formnumber = 0
+        self._footerpresent = 0
+        self._object = apply(canvas.Canvas, args, kwargs)
+        self._pyfile = cStringIO.StringIO()
+        self._PyWrite(PyHeader)
+        try :
+            del kwargs["filename"]
+        except KeyError :
+            pass
+        self._PyWrite("    # create the PDF document\n    %s = Canvas(file, %s)\n\n    # Begins page 1" % (self._name, apply(buildargs, args[1:], kwargs)))
+
+    def __nonzero__(self) :
+        """This is needed by platypus' tables."""
+        return 1
+
+    def __str__(self) :
+        """Returns the equivalent Python source code."""
+        if not self._footerpresent :
+            self._PyWrite(PyFooter)
+            self._footerpresent = 1
+        return self._pyfile.getvalue()
+
+    def __getattr__(self, name) :
+        """Method or attribute access."""
+        if name == "beginPath" :
+            return self.PathObject(self)
+        elif name == "beginText" :
+            return self.TextObject(self)
+        else :
+            return self.Action(self, name)
+
+    def _PyWrite(self, pycode) :
+        """Outputs the source code with a trailing newline."""
+        self._pyfile.write("%s\n" % pycode)
+
+if __name__ == '__main__':
+    print 'For test scripts, look in reportlab/test'