src/reportlab/pdfgen/pycanvas.py
changeset 2964 32352db0d71e
parent 1777 48870c5f865a
child 2966 c9df63ccabdf
equal deleted inserted replaced
2963:c414c0ab69e7 2964:32352db0d71e
       
     1 # a Pythonesque Canvas v0.8
       
     2 # Author : Jerome Alet - <alet@librelogiciel.com>
       
     3 # License : ReportLab's license
       
     4 #
       
     5 # $Id: pycanvas.py,v 1.8 2002/11/06 17:11:15 rgbecker Exp $
       
     6 #
       
     7 __doc__ = """pycanvas.Canvas : a Canvas class which can also output Python source code.
       
     8 
       
     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.
       
    13 
       
    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".
       
    17 
       
    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.
       
    27 
       
    28 the reportlab/test/test_pdfgen_pycanvas.py program is the test suite
       
    29 for pycanvas, you can do the following to run it :
       
    30 
       
    31     First set verbose=1 in reportlab/rl_config.py
       
    32 
       
    33     then from the command interpreter :
       
    34 
       
    35     $ cd reportlab/test
       
    36     $ python test_pdfgen_pycanvas.py >n1.py
       
    37 
       
    38     this will produce both n1.py and test_pdfgen_pycanvas.pdf
       
    39 
       
    40     then :
       
    41 
       
    42     $ python n1.py n1.pdf >n2.py
       
    43     $ python n2.py n2.pdf >n3.py
       
    44     $ ...
       
    45 
       
    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)
       
    51 
       
    52     n1.pdf, n2.pdf, n3.pdf and so on will be PDF files
       
    53     similar to test_pdfgen_pycanvas.pdf.
       
    54 
       
    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
       
    59     pythonsource = n1.doIt("myfile.pdf", regenerate=1)
       
    60 
       
    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")
       
    65 
       
    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 
       
    69     python n1.py
       
    70 
       
    71     will print the python source code equivalent to n1.py
       
    72 
       
    73 Why would you want to use such a beast ?
       
    74 
       
    75     - To linearize (serialize?) a program : optimizing some complex
       
    76       parts for example.
       
    77 
       
    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.
       
    84 
       
    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
       
    89       have Oracle).
       
    90 
       
    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.
       
    94 
       
    95     - ... Insert your own ideas here ...
       
    96 
       
    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 
       
   105 PyHeader = '''#! /usr/bin/env python
       
   106 
       
   107 #
       
   108 # This code was entirely generated by ReportLab (http://www.reportlab.com)
       
   109 #
       
   110 
       
   111 import sys
       
   112 from reportlab.pdfgen import pathobject
       
   113 from reportlab.pdfgen import textobject
       
   114 from reportlab.lib.colors import Color
       
   115 
       
   116 def doIt(file, regenerate=0) :
       
   117     """Generates a PDF document, save it into file.
       
   118 
       
   119        file : either a filename or a file-like object.
       
   120 
       
   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 '''
       
   131 
       
   132 PyFooter = '''
       
   133     # if we want the equivalent Python source code, then send it back
       
   134     if regenerate :
       
   135         return str(c)
       
   136 
       
   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])
       
   141         sys.exit(-1)
       
   142     else :
       
   143         # we've got a filename, we can proceed.
       
   144         print doIt(sys.argv[1], regenerate=1)
       
   145         sys.exit(0)'''
       
   146 
       
   147 def buildargs(*args, **kwargs) :
       
   148     """Constructs a printable list of arguments suitable for use in source function calls."""
       
   149     arguments = ""
       
   150     for arg in args :
       
   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]
       
   156     return arguments
       
   157 
       
   158 class PDFAction :
       
   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
       
   164 
       
   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)
       
   168 
       
   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 :
       
   172             self._precomment()
       
   173             self._parent._parent._PyWrite("    %s.%s(%s)" % (self._parent._name, self._action, apply(buildargs, args, kwargs)))
       
   174             self._postcomment()
       
   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
       
   178         return retcode
       
   179 
       
   180     def __hash__(self) :
       
   181         return hash(getattr(self._parent._object, self._action))
       
   182 
       
   183     def __coerce__(self, other) :
       
   184         """Needed."""
       
   185         return coerce(getattr(self._parent._object, self._action), other)
       
   186 
       
   187     def _precomment(self) :
       
   188         """To be overriden."""
       
   189         pass
       
   190 
       
   191     def _postcomment(self) :
       
   192         """To be overriden."""
       
   193         pass
       
   194 
       
   195 class PDFObject :
       
   196     """Base class for PDF objects like PDFPathObject and PDFTextObject."""
       
   197     _number = 0
       
   198     def __init__(self, parent) :
       
   199         """Saves a pointer to the parent Canvas."""
       
   200         self._parent = parent
       
   201         self._initdone = 0
       
   202 
       
   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)
       
   206 
       
   207     def __repr__(self) :
       
   208         """Returns the name used in the generated source code (e.g. 'p' or 't')."""
       
   209         return self._name
       
   210 
       
   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)))
       
   217             self._initdone = 1
       
   218         return self
       
   219 
       
   220 class Canvas :
       
   221     """Our fake Canvas class, which will intercept each and every method or attribute access."""
       
   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"
       
   227 
       
   228     class PathObject(PDFObject) :
       
   229         _name = "p"
       
   230         def _postinit(self, *args, **kwargs) :
       
   231             self._object = apply(pathobject.PDFPathObject, args, kwargs)
       
   232             return "beginPath"
       
   233 
       
   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" :
       
   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))
       
   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")
       
   257 
       
   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("")
       
   265 
       
   266     _name = "c"
       
   267     def __init__(self, *args, **kwargs) :
       
   268         """Initialize and begins source code."""
       
   269         self._parent = self     # nice trick, isn't it ?
       
   270         self._in = 0
       
   271         self._contextlevel = 0
       
   272         self._pagenumber = 1
       
   273         self._formnumber = 0
       
   274         self._footerpresent = 0
       
   275         self._object = apply(canvas.Canvas, args, kwargs)
       
   276         self._pyfile = cStringIO.StringIO()
       
   277         self._PyWrite(PyHeader)
       
   278         try :
       
   279             del kwargs["filename"]
       
   280         except KeyError :
       
   281             pass
       
   282         self._PyWrite("    # create the PDF document\n    %s = Canvas(file, %s)\n\n    # Begins page 1" % (self._name, apply(buildargs, args[1:], kwargs)))
       
   283 
       
   284     def __nonzero__(self) :
       
   285         """This is needed by platypus' tables."""
       
   286         return 1
       
   287 
       
   288     def __str__(self) :
       
   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()
       
   294 
       
   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)
       
   301         else :
       
   302             return self.Action(self, name)
       
   303 
       
   304     def _PyWrite(self, pycode) :
       
   305         """Outputs the source code with a trailing newline."""
       
   306         self._pyfile.write("%s\n" % pycode)
       
   307 
       
   308 if __name__ == '__main__':
       
   309     print 'For test scripts, look in reportlab/test'