From 1e479b40d638ce0303402b6e4af348e9ae8e8c68 Mon Sep 17 00:00:00 2001 From: pinky <> Date: Sun, 7 Jan 2007 23:35:53 +0000 Subject: [PATCH] RL2 bzr revid: pinky-223ff6e649c55e0032638fd654c6a1f5d1fdcf01 --- bin/reportlab/tools/README | 10 + bin/reportlab/tools/__init__.py | 3 + bin/reportlab/tools/docco/README | 8 + bin/reportlab/tools/docco/__init__.py | 3 + bin/reportlab/tools/docco/codegrab.py | 228 +++ bin/reportlab/tools/docco/docpy.py | 1247 +++++++++++++ bin/reportlab/tools/docco/examples.py | 851 +++++++++ bin/reportlab/tools/docco/graphdocpy.py | 980 +++++++++++ bin/reportlab/tools/docco/rl_doc_utils.py | 411 +++++ bin/reportlab/tools/docco/rltemplate.py | 135 ++ bin/reportlab/tools/docco/stylesheet.py | 165 ++ bin/reportlab/tools/docco/t_parse.py | 247 +++ bin/reportlab/tools/docco/yaml.py | 201 +++ bin/reportlab/tools/docco/yaml2pdf.py | 104 ++ bin/reportlab/tools/py2pdf/README | 8 + bin/reportlab/tools/py2pdf/__init__.py | 3 + bin/reportlab/tools/py2pdf/demo-config.txt | 11 + bin/reportlab/tools/py2pdf/demo.py | 198 +++ bin/reportlab/tools/py2pdf/idle_print.py | 56 + bin/reportlab/tools/py2pdf/py2pdf.py | 1567 +++++++++++++++++ bin/reportlab/tools/py2pdf/vertpython.jpg | Bin 0 -> 22872 bytes bin/reportlab/tools/pythonpoint/README | 29 + bin/reportlab/tools/pythonpoint/__init__.py | 3 + .../tools/pythonpoint/customshapes.py | 298 ++++ .../tools/pythonpoint/demos/LeERC___.AFM | 93 + .../tools/pythonpoint/demos/LeERC___.PFB | Bin 0 -> 48590 bytes .../tools/pythonpoint/demos/examples.py | 841 +++++++++ .../tools/pythonpoint/demos/figures.xml | 71 + bin/reportlab/tools/pythonpoint/demos/htu.xml | 96 + .../tools/pythonpoint/demos/leftlogo.a85 | 53 + .../tools/pythonpoint/demos/leftlogo.gif | Bin 0 -> 2198 bytes .../tools/pythonpoint/demos/lj8100.jpg | Bin 0 -> 12463 bytes .../tools/pythonpoint/demos/monterey.xml | 306 ++++ .../tools/pythonpoint/demos/outline.gif | Bin 0 -> 12918 bytes .../tools/pythonpoint/demos/pplogo.gif | Bin 0 -> 3429 bytes .../tools/pythonpoint/demos/python.gif | Bin 0 -> 125666 bytes .../tools/pythonpoint/demos/pythonpoint.xml | 1051 +++++++++++ .../tools/pythonpoint/demos/slidebox.py | 29 + .../tools/pythonpoint/demos/spectrum.png | Bin 0 -> 1855 bytes .../tools/pythonpoint/demos/vertpython.gif | Bin 0 -> 20910 bytes .../tools/pythonpoint/pythonpoint.dtd | 275 +++ .../tools/pythonpoint/pythonpoint.py | 1124 ++++++++++++ bin/reportlab/tools/pythonpoint/stdparser.py | 813 +++++++++ .../tools/pythonpoint/styles/__init__.py | 3 + .../tools/pythonpoint/styles/horrible.py | 101 ++ bin/reportlab/tools/pythonpoint/styles/htu.py | 158 ++ .../tools/pythonpoint/styles/modern.py | 120 ++ .../tools/pythonpoint/styles/projection.py | 106 ++ .../tools/pythonpoint/styles/standard.py | 132 ++ bin/reportlab/tools/utils/add_bleed.py | 28 + bin/reportlab/tools/utils/dumpttf.py | 60 + 51 files changed, 12226 insertions(+) create mode 100644 bin/reportlab/tools/README create mode 100644 bin/reportlab/tools/__init__.py create mode 100644 bin/reportlab/tools/docco/README create mode 100644 bin/reportlab/tools/docco/__init__.py create mode 100644 bin/reportlab/tools/docco/codegrab.py create mode 100644 bin/reportlab/tools/docco/docpy.py create mode 100644 bin/reportlab/tools/docco/examples.py create mode 100644 bin/reportlab/tools/docco/graphdocpy.py create mode 100644 bin/reportlab/tools/docco/rl_doc_utils.py create mode 100644 bin/reportlab/tools/docco/rltemplate.py create mode 100644 bin/reportlab/tools/docco/stylesheet.py create mode 100644 bin/reportlab/tools/docco/t_parse.py create mode 100644 bin/reportlab/tools/docco/yaml.py create mode 100644 bin/reportlab/tools/docco/yaml2pdf.py create mode 100644 bin/reportlab/tools/py2pdf/README create mode 100644 bin/reportlab/tools/py2pdf/__init__.py create mode 100644 bin/reportlab/tools/py2pdf/demo-config.txt create mode 100644 bin/reportlab/tools/py2pdf/demo.py create mode 100644 bin/reportlab/tools/py2pdf/idle_print.py create mode 100644 bin/reportlab/tools/py2pdf/py2pdf.py create mode 100644 bin/reportlab/tools/py2pdf/vertpython.jpg create mode 100644 bin/reportlab/tools/pythonpoint/README create mode 100644 bin/reportlab/tools/pythonpoint/__init__.py create mode 100644 bin/reportlab/tools/pythonpoint/customshapes.py create mode 100644 bin/reportlab/tools/pythonpoint/demos/LeERC___.AFM create mode 100644 bin/reportlab/tools/pythonpoint/demos/LeERC___.PFB create mode 100644 bin/reportlab/tools/pythonpoint/demos/examples.py create mode 100644 bin/reportlab/tools/pythonpoint/demos/figures.xml create mode 100644 bin/reportlab/tools/pythonpoint/demos/htu.xml create mode 100644 bin/reportlab/tools/pythonpoint/demos/leftlogo.a85 create mode 100644 bin/reportlab/tools/pythonpoint/demos/leftlogo.gif create mode 100644 bin/reportlab/tools/pythonpoint/demos/lj8100.jpg create mode 100644 bin/reportlab/tools/pythonpoint/demos/monterey.xml create mode 100644 bin/reportlab/tools/pythonpoint/demos/outline.gif create mode 100644 bin/reportlab/tools/pythonpoint/demos/pplogo.gif create mode 100644 bin/reportlab/tools/pythonpoint/demos/python.gif create mode 100644 bin/reportlab/tools/pythonpoint/demos/pythonpoint.xml create mode 100644 bin/reportlab/tools/pythonpoint/demos/slidebox.py create mode 100644 bin/reportlab/tools/pythonpoint/demos/spectrum.png create mode 100644 bin/reportlab/tools/pythonpoint/demos/vertpython.gif create mode 100644 bin/reportlab/tools/pythonpoint/pythonpoint.dtd create mode 100644 bin/reportlab/tools/pythonpoint/pythonpoint.py create mode 100644 bin/reportlab/tools/pythonpoint/stdparser.py create mode 100644 bin/reportlab/tools/pythonpoint/styles/__init__.py create mode 100644 bin/reportlab/tools/pythonpoint/styles/horrible.py create mode 100644 bin/reportlab/tools/pythonpoint/styles/htu.py create mode 100644 bin/reportlab/tools/pythonpoint/styles/modern.py create mode 100644 bin/reportlab/tools/pythonpoint/styles/projection.py create mode 100644 bin/reportlab/tools/pythonpoint/styles/standard.py create mode 100644 bin/reportlab/tools/utils/add_bleed.py create mode 100644 bin/reportlab/tools/utils/dumpttf.py diff --git a/bin/reportlab/tools/README b/bin/reportlab/tools/README new file mode 100644 index 00000000000..f5f75d08de3 --- /dev/null +++ b/bin/reportlab/tools/README @@ -0,0 +1,10 @@ +This directory is the home of various ReportLab tools. +They are packaged such that they can be used more easily. + +Tool candidates are: + + - PythonPoint + - docpy.py + - graphdocpy.py + - ... + diff --git a/bin/reportlab/tools/__init__.py b/bin/reportlab/tools/__init__.py new file mode 100644 index 00000000000..da52b52d322 --- /dev/null +++ b/bin/reportlab/tools/__init__.py @@ -0,0 +1,3 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/__init__.py diff --git a/bin/reportlab/tools/docco/README b/bin/reportlab/tools/docco/README new file mode 100644 index 00000000000..b23b5661348 --- /dev/null +++ b/bin/reportlab/tools/docco/README @@ -0,0 +1,8 @@ +This directory contains a number of tools to do with documentation and +documentation building. + +Some of these are our own internal tools which we use for building the +ReportLab documentation. Some tools are obsolete and will be removed +in due course. + +In the mean time, use at your own risk. diff --git a/bin/reportlab/tools/docco/__init__.py b/bin/reportlab/tools/docco/__init__.py new file mode 100644 index 00000000000..3a0b73da24d --- /dev/null +++ b/bin/reportlab/tools/docco/__init__.py @@ -0,0 +1,3 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/__init__.py diff --git a/bin/reportlab/tools/docco/codegrab.py b/bin/reportlab/tools/docco/codegrab.py new file mode 100644 index 00000000000..87a580a2a3e --- /dev/null +++ b/bin/reportlab/tools/docco/codegrab.py @@ -0,0 +1,228 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/codegrab.py +#codegrab.py +""" +This grabs various Python class, method and function +headers and their doc strings to include in documents +""" + +import imp +import types +import string +import os +import sys + +class Struct: + pass + +def getObjectsDefinedIn(modulename, directory=None): + """Returns two tuple of (functions, classes) defined + in the given module. 'directory' must be the directory + containing the script; modulename should not include + the .py suffix""" + + if directory: + searchpath = [directory] + else: + searchpath = sys.path # searches usual Python path + + #might be a package. If so, check the top level + #package is there, then recalculate the path needed + words = string.split(modulename, '.') + if len(words) > 1: + packagename = words[0] + packagefound = imp.find_module(packagename, searchpath) + assert packagefound, "Package %s not found" % packagename + (file, packagepath, description) = packagefound + #now the full path should be known, if it is in the + #package + + directory = apply(os.path.join, tuple([packagepath] + words[1:-1])) + modulename = words[-1] + searchpath = [directory] + + + + #find and import the module. + found = imp.find_module(modulename, searchpath) + assert found, "Module %s not found" % modulename + (file, pathname, description) = found + mod = imp.load_module(modulename, file, pathname, description) + + #grab the code too, minus trailing newlines + lines = open(pathname, 'r').readlines() + lines = map(string.rstrip, lines) + + result = Struct() + result.functions = [] + result.classes = [] + result.doc = mod.__doc__ + for name in dir(mod): + value = getattr(mod, name) + if type(value) is types.FunctionType: + path, file = os.path.split(value.func_code.co_filename) + root, ext = os.path.splitext(file) + #we're possibly interested in it + if root == modulename: + #it was defined here + funcObj = value + fn = Struct() + fn.name = name + fn.proto = getFunctionPrototype(funcObj, lines) + if funcObj.__doc__: + fn.doc = dedent(funcObj.__doc__) + else: + fn.doc = '(no documentation string)' + #is it official? + if name[0:1] == '_': + fn.status = 'private' + elif name[-1] in '0123456789': + fn.status = 'experimental' + else: + fn.status = 'official' + + result.functions.append(fn) + elif type(value) == types.ClassType: + if value.__module__ == modulename: + cl = Struct() + cl.name = name + if value.__doc__: + cl.doc = dedent(value.__doc__) + else: + cl.doc = "(no documentation string)" + + cl.bases = [] + for base in value.__bases__: + cl.bases.append(base.__name__) + if name[0:1] == '_': + cl.status = 'private' + elif name[-1] in '0123456789': + cl.status = 'experimental' + else: + cl.status = 'official' + + cl.methods = [] + #loop over dict finding methods defined here + # Q - should we show all methods? + # loop over dict finding methods defined here + items = value.__dict__.items() + items.sort() + for (key2, value2) in items: + if type(value2) <> types.FunctionType: + continue # not a method + elif os.path.splitext(value2.func_code.co_filename)[0] == modulename: + continue # defined in base class + else: + #we want it + meth = Struct() + meth.name = key2 + name2 = value2.func_code.co_name + meth.proto = getFunctionPrototype(value2, lines) + if name2!=key2: + meth.doc = 'pointer to '+name2 + meth.proto = string.replace(meth.proto,name2,key2) + else: + if value2.__doc__: + meth.doc = dedent(value2.__doc__) + else: + meth.doc = "(no documentation string)" + #is it official? + if key2[0:1] == '_': + meth.status = 'private' + elif key2[-1] in '0123456789': + meth.status = 'experimental' + else: + meth.status = 'official' + cl.methods.append(meth) + result.classes.append(cl) + return result + +def getFunctionPrototype(f, lines): + """Pass in the function object and list of lines; + it extracts the header as a multiline text block.""" + firstLineNo = f.func_code.co_firstlineno - 1 + lineNo = firstLineNo + brackets = 0 + while 1: + line = lines[lineNo] + for char in line: + if char == '(': + brackets = brackets + 1 + elif char == ')': + brackets = brackets - 1 + if brackets == 0: + break + else: + lineNo = lineNo + 1 + + usefulLines = lines[firstLineNo:lineNo+1] + return string.join(usefulLines, '\n') + + +def dedent(comment): + """Attempts to dedent the lines to the edge. Looks at no. + of leading spaces in line 2, and removes up to that number + of blanks from other lines.""" + commentLines = string.split(comment, '\n') + if len(commentLines) < 2: + cleaned = map(string.lstrip, commentLines) + else: + spc = 0 + for char in commentLines[1]: + if char in string.whitespace: + spc = spc + 1 + else: + break + #now check other lines + cleaned = [] + for line in commentLines: + for i in range(min(len(line),spc)): + if line[0] in string.whitespace: + line = line[1:] + cleaned.append(line) + return string.join(cleaned, '\n') + + + +def dumpDoc(modulename, directory=None): + """Test support. Just prints docco on the module + to standard output.""" + docco = getObjectsDefinedIn(modulename, directory) + print 'codegrab.py - ReportLab Documentation Utility' + print 'documenting', modulename + '.py' + print '-------------------------------------------------------' + print + if docco.functions == []: + print 'No functions found' + else: + print 'Functions:' + for f in docco.functions: + print f.proto + print ' ' + f.doc + + if docco.classes == []: + print 'No classes found' + else: + print 'Classes:' + for c in docco.classes: + print c.name + print ' ' + c.doc + for m in c.methods: + print m.proto # it is already indented in the file! + print ' ' + m.doc + print + +def test(m='reportlab.platypus.paragraph'): + dumpDoc(m) + +if __name__=='__main__': + import sys + print 'Path to search:' + for line in sys.path: + print ' ',line + M = sys.argv[1:] + if M==[]: + M.append('reportlab.platypus.paragraph') + for m in M: + test(m) \ No newline at end of file diff --git a/bin/reportlab/tools/docco/docpy.py b/bin/reportlab/tools/docco/docpy.py new file mode 100644 index 00000000000..75a77efe773 --- /dev/null +++ b/bin/reportlab/tools/docco/docpy.py @@ -0,0 +1,1247 @@ +#!/usr/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/docpy.py + +"""Generate documentation from live Python objects. + +This is an evolving module that allows to generate documentation +for python modules in an automated fashion. The idea is to take +live Python objects and inspect them in order to use as much mean- +ingful information as possible to write in some formatted way into +different types of documents. + +In principle a skeleton captures the gathered information and +makes it available via a certain API to formatters that use it +in whatever way they like to produce something of interest. The +API allows for adding behaviour in subclasses of these formatters, +such that, e.g. for certain classes it is possible to trigger +special actions like displaying a sample image of a class that +represents some graphical widget, say. + +Type the following for usage info: + + python docpy.py -h +""" + +# Much inspired by Ka-Ping Yee's htmldoc.py. +# Needs the inspect module. + +# Dinu Gherman + + +__version__ = '0.8' + + +import sys, os, re, types, string, getopt, copy, time +from string import find, join, split, replace, expandtabs, rstrip + +from reportlab.pdfgen import canvas +from reportlab.lib import colors +from reportlab.lib.units import inch, cm +from reportlab.lib.pagesizes import A4 +from reportlab.lib import enums +from reportlab.lib.enums import TA_CENTER, TA_LEFT +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus.flowables import Flowable, Spacer +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.flowables \ + import Flowable, Preformatted,Spacer, Image, KeepTogether, PageBreak +from reportlab.platypus.tableofcontents import TableOfContents +from reportlab.platypus.xpreformatted import XPreformatted +from reportlab.platypus.frames import Frame +from reportlab.platypus.doctemplate \ + import PageTemplate, BaseDocTemplate +from reportlab.platypus.tables import TableStyle, Table +import inspect + +#################################################################### +# +# Stuff needed for building PDF docs. +# +#################################################################### + + +def mainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + + pageNumber = canvas.getPageNumber() + canvas.line(2*cm, A4[1]-2*cm, A4[0]-2*cm, A4[1]-2*cm) + canvas.line(2*cm, 2*cm, A4[0]-2*cm, 2*cm) + if pageNumber > 1: + canvas.setFont('Times-Roman', 12) + canvas.drawString(4 * inch, cm, "%d" % pageNumber) + if hasattr(canvas, 'headerLine'): # hackish + headerline = string.join(canvas.headerLine, ' \215 ') # bullet + canvas.drawString(2*cm, A4[1]-1.75*cm, headerline) + + canvas.setFont('Times-Roman', 8) + msg = "Generated with reportlab.lib.docpy. See http://www.reportlab.com!" + canvas.drawString(2*cm, 1.65*cm, msg) + + canvas.restoreState() + + +class MyTemplate(BaseDocTemplate): + "The document template used for all PDF documents." + + _invalidInitArgs = ('pageTemplates',) + + def __init__(self, filename, **kw): + frame1 = Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1') + self.allowSplitting = 0 + apply(BaseDocTemplate.__init__, (self, filename), kw) + self.addPageTemplates(PageTemplate('normal', [frame1], mainPageFrame)) + + + def afterFlowable(self, flowable): + "Takes care of header line, TOC and outline entries." + + if flowable.__class__.__name__ == 'Paragraph': + f = flowable + name7 = f.style.name[:7] + name8 = f.style.name[:8] + + # Build a list of heading parts. + # So far, this is the *last* item on the *previous* page... + if name7 == 'Heading' and not hasattr(self.canv, 'headerLine'): + self.canv.headerLine = [] + + if name8 == 'Heading0': + self.canv.headerLine = [f.text] # hackish + elif name8 == 'Heading1': + if len(self.canv.headerLine) == 2: + del self.canv.headerLine[-1] + elif len(self.canv.headerLine) == 3: + del self.canv.headerLine[-1] + del self.canv.headerLine[-1] + self.canv.headerLine.append(f.text) + elif name8 == 'Heading2': + if len(self.canv.headerLine) == 3: + del self.canv.headerLine[-1] + self.canv.headerLine.append(f.text) + + if name7 == 'Heading': + # Register TOC entries. + headLevel = int(f.style.name[7:]) + self.notify('TOCEntry', (headLevel, flowable.getPlainText(), self.page)) + + # Add PDF outline entries. + c = self.canv + title = f.text + key = str(hash(f)) + + try: + if headLevel == 0: + isClosed = 0 + else: + isClosed = 1 + + c.bookmarkPage(key) + c.addOutlineEntry(title, key, level=headLevel, + closed=isClosed) + except ValueError: + pass + + +#################################################################### +# +# Utility functions (Ka-Ping Yee). +# +#################################################################### + +def htmlescape(text): + "Escape special HTML characters, namely &, <, >." + return replace(replace(replace(text, '&', '&'), + '<', '<'), + '>', '>') + +def htmlrepr(object): + return htmlescape(repr(object)) + + +def defaultformat(object): + return '=' + htmlrepr(object) + + +def getdoc(object): + result = inspect.getdoc(object) + if not result: + try: + result = inspect.getcomments(object) + except: + pass + return result and rstrip(result) + '\n' or '' + + +def reduceDocStringLength(docStr): + "Return first line of a multiline string." + + return split(docStr, '\n')[0] + + +#################################################################### +# +# More utility functions +# +#################################################################### + +def makeHtmlSection(text, bgcolor='#FFA0FF'): + """Create HTML code for a section. + + This is usually a header for all classes or functions. +u """ + text = htmlescape(expandtabs(text)) + result = [] + result.append("""""") + result.append("""
""" % bgcolor) + result.append("""

%s

""" % text) + result.append("""
""") + result.append('') + + return join(result, '\n') + + +def makeHtmlSubSection(text, bgcolor='#AAA0FF'): + """Create HTML code for a subsection. + + This is usually a class or function name. + """ + text = htmlescape(expandtabs(text)) + result = [] + result.append("""""") + result.append("""
""" % bgcolor) + result.append("""

%s

""" % text) + result.append("""
""") + result.append('') + + return join(result, '\n') + + +def makeHtmlInlineImage(text): + """Create HTML code for an inline image. + """ + + return """%s""" % (text, text) + + +#################################################################### +# +# Core "standard" docpy classes +# +#################################################################### + +class PackageSkeleton0: + """A class collecting 'interesting' information about a package.""" + pass # Not yet! + + +class ModuleSkeleton0: + """A class collecting 'interesting' information about a module.""" + + def __init__(self): + # This is an ad-hoc, somewhat questionable 'data structure', + # but for the time being it serves its purpose and is fairly + # self-contained. + self.module = {} + self.functions = {} + self.classes = {} + + + # Might need more like this, later. + def getModuleName(self): + """Return the name of the module being treated.""" + + return self.module['name'] + + + # These inspect methods all rely on the inspect module. + def inspect(self, object): + """Collect information about a given object.""" + + self.moduleSpace = object + + # Very non-OO, left for later... + if inspect.ismodule(object): + self._inspectModule(object) + elif inspect.isclass(object): + self._inspectClass(object) + elif inspect.ismethod(object): + self._inspectMethod(object) + elif inspect.isfunction(object): + self._inspectFunction(object) + elif inspect.isbuiltin(object): + self._inspectBuiltin(object) + else: + msg = "Don't know how to document this kind of object." + raise TypeError, msg + + + def _inspectModule(self, object): + """Collect information about a given module object.""" + name = object.__name__ + + self.module['name'] = name + if hasattr(object, '__version__'): + self.module['version'] = object.__version__ + + cadr = lambda list: list[1] + modules = map(cadr, inspect.getmembers(object, inspect.ismodule)) + + classes, cdict = [], {} + for key, value in inspect.getmembers(object, inspect.isclass): + if (inspect.getmodule(value) or object) is object: + classes.append(value) + cdict[key] = cdict[value] = '#' + key + + functions, fdict = [], {} + for key, value in inspect.getmembers(object, inspect.isroutine): + #if inspect.isbuiltin(value) or inspect.getmodule(value) is object: + functions.append(value) + fdict[key] = '#-' + key + if inspect.isfunction(value): fdict[value] = fdict[key] + + for c in classes: + for base in c.__bases__: + key, modname = base.__name__, base.__module__ + if modname != name and sys.modules.has_key(modname): + module = sys.modules[modname] + if hasattr(module, key) and getattr(module, key) is base: + if not cdict.has_key(key): + cdict[key] = cdict[base] = modname + '.txt#' + key + +## doc = getdoc(object) or 'No doc string.' + doc = getdoc(object) + self.module['doc'] = doc + + if modules: + self.module['importedModules'] = map(lambda m:m.__name__, modules) + + if classes: + for item in classes: + self._inspectClass(item, fdict, cdict) + + if functions: + for item in functions: + self._inspectFunction(item, fdict, cdict) + + + def _inspectClass(self, object, functions={}, classes={}): + """Collect information about a given class object.""" + + name = object.__name__ + bases = object.__bases__ + results = [] + + if bases: + parents = [] + for base in bases: + parents.append(base) + + self.classes[name] = {} + if bases: + self.classes[name]['bases'] = parents + + methods, mdict = [], {} + for key, value in inspect.getmembers(object, inspect.ismethod): + methods.append(value) + mdict[key] = mdict[value] = '#' + name + '-' + key + + if methods: + if not self.classes[name].has_key('methods'): + self.classes[name]['methods'] = {} + for item in methods: + self._inspectMethod(item, functions, classes, mdict, name) + +## doc = getdoc(object) or 'No doc string.' + doc = getdoc(object) + self.classes[name]['doc'] = doc + + + def _inspectMethod(self, object, functions={}, classes={}, methods={}, clname=''): + """Collect information about a given method object.""" + + self._inspectFunction(object.im_func, functions, classes, methods, clname) + + + def _inspectFunction(self, object, functions={}, classes={}, methods={}, clname=''): + """Collect information about a given function object.""" + try: + args, varargs, varkw, defaults = inspect.getargspec(object) + argspec = inspect.formatargspec( + args, varargs, varkw, defaults, + defaultformat=defaultformat) + except TypeError: + argspec = '( ... )' + +## doc = getdoc(object) or 'No doc string.' + doc = getdoc(object) + + if object.__name__ == '': + decl = [' lambda ', argspec[1:-1]] + # print ' %s' % decl + # Do something with lambda functions as well... + # ... + else: + decl = object.__name__ + if not clname: + self.functions[object.__name__] = {'signature':argspec, 'doc':doc} + else: + theMethods = self.classes[clname]['methods'] + if not theMethods.has_key(object.__name__): + theMethods[object.__name__] = {} + + theMethod = theMethods[object.__name__] + theMethod['signature'] = argspec + theMethod['doc'] = doc + + + def _inspectBuiltin(self, object): + """Collect information about a given built-in.""" + + print object.__name__ + '( ... )' + + + def walk(self, formatter): + """Call event methods in a visiting formatter.""" + + s = self + f = formatter + + # The order is fixed, but could be made flexible + # with one more template method... + + # Module + modName = s.module['name'] + modDoc = s.module['doc'] + imported = s.module.get('importedModules', []) + imported.sort() + # f.indentLevel = f.indentLevel + 1 + f.beginModule(modName, modDoc, imported) + + # Classes + f.indentLevel = f.indentLevel + 1 + f.beginClasses(s.classes.keys()) + items = s.classes.items() + items.sort() + for k, v in items: + cDoc = s.classes[k]['doc'] + bases = s.classes[k].get('bases', []) + f.indentLevel = f.indentLevel + 1 + f.beginClass(k, cDoc, bases) + + # This if should move out of this method. + if not s.classes[k].has_key('methods'): + s.classes[k]['methods'] = {} + + # Methods + #f.indentLevel = f.indentLevel + 1 + f.beginMethods(s.classes[k]['methods'].keys()) + items = s.classes[k]['methods'].items() + items.sort() + for m, v in items: + mDoc = v['doc'] + sig = v['signature'] + f.indentLevel = f.indentLevel + 1 + f.beginMethod(m, mDoc, sig) + f.indentLevel = f.indentLevel - 1 + f.endMethod(m, mDoc, sig) + + #f.indentLevel = f.indentLevel - 1 + f.endMethods(s.classes[k]['methods'].keys()) + + f.indentLevel = f.indentLevel - 1 + f.endClass(k, cDoc, bases) + + # And what about attributes?! + + f.indentLevel = f.indentLevel - 1 + f.endClasses(s.classes.keys()) + + # Functions + f.indentLevel = f.indentLevel + 1 + f.beginFunctions(s.functions.keys()) + items = s.functions.items() + items.sort() + for k, v in items: + doc = v['doc'] + sig = v['signature'] + f.indentLevel = f.indentLevel + 1 + f.beginFunction(k, doc, sig) + f.indentLevel = f.indentLevel - 1 + f.endFunction(k, doc, sig) + f.indentLevel = f.indentLevel - 1 + f.endFunctions(s.functions.keys()) + + #f.indentLevel = f.indentLevel - 1 + f.endModule(modName, modDoc, imported) + + # Constants?! + + +#################################################################### +# +# Core "standard" docpy document builders +# +#################################################################### + +class DocBuilder0: + """An abstract class to document the skeleton of a Python module. + + Instances take a skeleton instance s and call their s.walk() + method. The skeleton, in turn, will walk over its tree structure + while generating events and calling equivalent methods from a + specific interface (begin/end methods). + """ + + fileSuffix = None + + def __init__(self, skeleton=None): + self.skeleton = skeleton + self.packageName = None + self.indentLevel = 0 + + + def write(self, skeleton=None): + if skeleton: + self.skeleton = skeleton + self.skeleton.walk(self) + + + # Event-method API, called by associated skeleton instances. + # In fact, these should raise a NotImplementedError, but for now we + # just don't do anything here. + + # The following four methods are *not* called by skeletons! + def begin(self, name='', typ=''): pass + def end(self): pass + + # Methods for packaging should move into a future PackageSkeleton... + def beginPackage(self, name): + self.packageName = name + + def endPackage(self, name): + pass + + # Only this subset is really called by associated skeleton instances. + + def beginModule(self, name, doc, imported): pass + def endModule(self, name, doc, imported): pass + + def beginClasses(self, names): pass + def endClasses(self, names): pass + + def beginClass(self, name, doc, bases): pass + def endClass(self, name, doc, bases): pass + + def beginMethods(self, names): pass + def endMethods(self, names): pass + + def beginMethod(self, name, doc, sig): pass + def endMethod(self, name, doc, sig): pass + + def beginFunctions(self, names): pass + def endFunctions(self, names): pass + + def beginFunction(self, name, doc, sig): pass + def endFunction(self, name, doc, sig): pass + + +class AsciiDocBuilder0(DocBuilder0): + """Document the skeleton of a Python module in ASCII format. + + The output will be an ASCII file with nested lines representing + the hiearchical module structure. + + Currently, no doc strings are listed. + """ + + fileSuffix = '.txt' + outLines = [] + indentLabel = ' ' + + def end(self): + # This if should move into DocBuilder0... + if self.packageName: + self.outPath = self.packageName + self.fileSuffix + elif self.skeleton: + self.outPath = self.skeleton.getModuleName() + self.fileSuffix + else: + self.outPath = '' + + if self.outPath: + file = open(self.outPath, 'w') + for line in self.outLines: + file.write(line + '\n') + file.close() + + + def beginPackage(self, name): + DocBuilder0.beginPackage(self, name) + lev, label = self.indentLevel, self.indentLabel + self.outLines.append('%sPackage: %s' % (lev*label, name)) + self.outLines.append('') + + + def beginModule(self, name, doc, imported): + append = self.outLines.append + lev, label = self.indentLevel, self.indentLabel + self.outLines.append('%sModule: %s' % (lev*label, name)) +## self.outLines.append('%s%s' % ((lev+1)*label, reduceDocStringLength(doc))) + append('') + + if imported: + self.outLines.append('%sImported' % ((lev+1)*label)) + append('') + for m in imported: + self.outLines.append('%s%s' % ((lev+2)*label, m)) + append('') + + + def beginClasses(self, names): + if names: + lev, label = self.indentLevel, self.indentLabel + self.outLines.append('%sClasses' % (lev*label)) + self.outLines.append('') + + + def beginClass(self, name, doc, bases): + append = self.outLines.append + lev, label = self.indentLevel, self.indentLabel + + if bases: + bases = map(lambda b:b.__name__, bases) # hack + append('%s%s(%s)' % (lev*label, name, join(bases, ', '))) + else: + append('%s%s' % (lev*label, name)) + return + +## append('%s%s' % ((lev+1)*label, reduceDocStringLength(doc))) + self.outLines.append('') + + + def endClass(self, name, doc, bases): + self.outLines.append('') + + + def beginMethod(self, name, doc, sig): + append = self.outLines.append + lev, label = self.indentLevel, self.indentLabel + append('%s%s%s' % (lev*label, name, sig)) +## append('%s%s' % ((lev+1)*label, reduceDocStringLength(doc))) +## append('') + + + def beginFunctions(self, names): + if names: + lev, label = self.indentLevel, self.indentLabel + self.outLines.append('%sFunctions' % (lev*label)) + self.outLines.append('') + + + def endFunctions(self, names): + self.outLines.append('') + + + def beginFunction(self, name, doc, sig): + append = self.outLines.append + lev, label = self.indentLevel, self.indentLabel + self.outLines.append('%s%s%s' % (lev*label, name, sig)) +## append('%s%s' % ((lev+1)*label, reduceDocStringLength(doc))) +## append('') + + +class HtmlDocBuilder0(DocBuilder0): + "A class to write the skeleton of a Python source in HTML format." + + fileSuffix = '.html' + outLines = [] + + def begin(self, name='', typ=''): + self.outLines.append("""""") + self.outLines.append("""""") + + + def end(self): + if self.packageName: + self.outPath = self.packageName + self.fileSuffix + elif self.skeleton: + self.outPath = self.skeleton.getModuleName() + self.fileSuffix + else: + self.outPath = '' + + if self.outPath: + file = open(self.outPath, 'w') + self.outLines.append('') + for line in self.outLines: + file.write(line + '\n') + file.close() + + + def beginPackage(self, name): + DocBuilder0.beginPackage(self, name) + + self.outLines.append("""%s""" % name) + self.outLines.append("""""") + self.outLines.append("""

%s

""" % name) + self.outLines.append('') + + + def beginModule(self, name, doc, imported): + if not self.packageName: + self.outLines.append("""%s""" % name) + self.outLines.append("""""") + + self.outLines.append("""

%s

""" % name) + self.outLines.append('') + for line in split(doc, '\n'): + self.outLines.append("""%s""" % htmlescape(line)) + self.outLines.append('
') + self.outLines.append('') + + if imported: + self.outLines.append(makeHtmlSection('Imported Modules')) + self.outLines.append("""""") + + + def beginClasses(self, names): + self.outLines.append(makeHtmlSection('Classes')) + + + def beginClass(self, name, doc, bases): + DocBuilder0.beginClass(self, name, doc, bases) + +## # Keep an eye on the base classes. +## self.currentBaseClasses = bases + + if bases: + bases = map(lambda b:b.__name__, bases) # hack + self.outLines.append(makeHtmlSubSection('%s(%s)' % (name, join(bases, ', ')))) + else: + self.outLines.append(makeHtmlSubSection('%s' % name)) + for line in split(doc, '\n'): + self.outLines.append("""%s""" % htmlescape(line)) + self.outLines.append('
') + + self.outLines.append('') + + + def beginMethods(self, names): + pass +## if names: +## self.outLines.append('

Method Interface

') +## self.outLines.append('') + + + def beginMethod(self, name, doc, sig): + self.beginFunction(name, doc, sig) + + + def beginFunctions(self, names): + self.outLines.append(makeHtmlSection('Functions')) + + + def beginFunction(self, name, doc, sig): + append = self.outLines.append + append("""
%s%s
""" % (name, sig)) + append('') + for line in split(doc, '\n'): + append("""
%s
""" % htmlescape(line)) + append('
') + append('
') + append('') + + +class PdfDocBuilder0(DocBuilder0): + "Document the skeleton of a Python module in PDF format." + + fileSuffix = '.pdf' + + def makeHeadingStyle(self, level, typ=None, doc=''): + "Make a heading style for different types of module content." + + if typ in ('package', 'module', 'class'): + style = ParagraphStyle(name='Heading'+str(level), + fontName = 'Courier-Bold', + fontSize=14, + leading=18, + spaceBefore=12, + spaceAfter=6) + elif typ in ('method', 'function'): + if doc: + style = ParagraphStyle(name='Heading'+str(level), + fontName = 'Courier-Bold', + fontSize=12, + leading=18, + firstLineIndent=-18, + leftIndent=36, + spaceBefore=0, + spaceAfter=-3) + else: + style = ParagraphStyle(name='Heading'+str(level), + fontName = 'Courier-Bold', + fontSize=12, + leading=18, + firstLineIndent=-18, + leftIndent=36, + spaceBefore=0, + spaceAfter=0) + + else: + style = ParagraphStyle(name='Heading'+str(level), + fontName = 'Times-Bold', + fontSize=14, + leading=18, + spaceBefore=12, + spaceAfter=6) + + return style + + + def begin(self, name='', typ=''): + styleSheet = getSampleStyleSheet() + self.code = styleSheet['Code'] + self.bt = styleSheet['BodyText'] + self.story = [] + + # Cover page + t = time.gmtime(time.time()) + timeString = time.strftime("%Y-%m-%d %H:%M", t) + self.story.append(Paragraph('Documentation for %s "%s"' % (typ, name), self.bt)) + self.story.append(Paragraph('Generated by: docpy.py version %s' % __version__, self.bt)) + self.story.append(Paragraph('Date generated: %s' % timeString, self.bt)) + self.story.append(Paragraph('Format: PDF', self.bt)) + self.story.append(PageBreak()) + + # Table of contents + toc = TableOfContents() + self.story.append(toc) + self.story.append(PageBreak()) + + + def end(self): + if self.outPath is not None: + pass + elif self.packageName: + self.outPath = self.packageName + self.fileSuffix + elif self.skeleton: + self.outPath = self.skeleton.getModuleName() + self.fileSuffix + else: + self.outPath = '' + print 'output path is %s' % self.outPath + if self.outPath: + doc = MyTemplate(self.outPath) + doc.multiBuild(self.story) + + + def beginPackage(self, name): + DocBuilder0.beginPackage(self, name) + story = self.story + story.append(Paragraph(name, self.makeHeadingStyle(self.indentLevel, 'package'))) + + + def beginModule(self, name, doc, imported): + story = self.story + bt = self.bt + story.append(Paragraph(name, self.makeHeadingStyle(self.indentLevel, 'module'))) + if doc: + story.append(XPreformatted(htmlescape(doc), bt)) + story.append(XPreformatted('', bt)) + + if imported: + story.append(Paragraph('Imported modules', self.makeHeadingStyle(self.indentLevel + 1))) + for m in imported: + p = Paragraph('\201 %s' % m, bt) + p.style.bulletIndent = 10 + p.style.leftIndent = 18 + story.append(p) + + + def endModule(self, name, doc, imported): + DocBuilder0.endModule(self, name, doc, imported) + self.story.append(PageBreak()) + + + def beginClasses(self, names): + self.story.append(Paragraph('Classes', self.makeHeadingStyle(self.indentLevel))) + + + def beginClass(self, name, doc, bases): + bt = self.bt + story = self.story + if bases: + bases = map(lambda b:b.__name__, bases) # hack + story.append(Paragraph('%s(%s)' % (name, join(bases, ', ')), self.makeHeadingStyle(self.indentLevel, 'class'))) + else: + story.append(Paragraph(name, self.makeHeadingStyle(self.indentLevel, 'class'))) + + if doc: + story.append(XPreformatted(htmlescape(doc), bt)) + story.append(XPreformatted('', bt)) + + + def beginMethod(self, name, doc, sig): + bt = self.bt + story = self.story + story.append(Paragraph(name+sig, self.makeHeadingStyle(self.indentLevel, 'method', doc))) + if doc: + story.append(XPreformatted(htmlescape(doc), bt)) + story.append(XPreformatted('', bt)) + + + def beginFunctions(self, names): + if names: + self.story.append(Paragraph('Functions', self.makeHeadingStyle(self.indentLevel))) + + + def beginFunction(self, name, doc, sig): + bt = self.bt + story = self.story + story.append(Paragraph(name+sig, self.makeHeadingStyle(self.indentLevel, 'function'))) + if doc: + story.append(XPreformatted(htmlescape(doc), bt)) + story.append(XPreformatted('', bt)) + + +class UmlPdfDocBuilder0(PdfDocBuilder0): + "Document the skeleton of a Python module with UML class diagrams." + + fileSuffix = '.pdf' + + def begin(self, name='', typ=''): + styleSheet = getSampleStyleSheet() + self.h1 = styleSheet['Heading1'] + self.h2 = styleSheet['Heading2'] + self.h3 = styleSheet['Heading3'] + self.code = styleSheet['Code'] + self.bt = styleSheet['BodyText'] + self.story = [] + self.classCompartment = '' + self.methodCompartment = [] + + + def beginModule(self, name, doc, imported): + story = self.story + h1, h2, h3, bt = self.h1, self.h2, self.h3, self.bt + styleSheet = getSampleStyleSheet() + bt1 = styleSheet['BodyText'] + + story.append(Paragraph(name, h1)) + story.append(XPreformatted(doc, bt1)) + + if imported: + story.append(Paragraph('Imported modules', self.makeHeadingStyle(self.indentLevel + 1))) + for m in imported: + p = Paragraph('\201 %s' % m, bt1) + p.style.bulletIndent = 10 + p.style.leftIndent = 18 + story.append(p) + + + def endModule(self, name, doc, imported): + self.story.append(PageBreak()) + PdfDocBuilder0.endModule(self, name, doc, imported) + + + def beginClasses(self, names): + h1, h2, h3, bt = self.h1, self.h2, self.h3, self.bt + if names: + self.story.append(Paragraph('Classes', h2)) + + + def beginClass(self, name, doc, bases): + self.classCompartment = '' + self.methodCompartment = [] + + if bases: + bases = map(lambda b:b.__name__, bases) # hack + self.classCompartment = '%s(%s)' % (name, join(bases, ', ')) + else: + self.classCompartment = name + + + def endClass(self, name, doc, bases): + h1, h2, h3, bt, code = self.h1, self.h2, self.h3, self.bt, self.code + styleSheet = getSampleStyleSheet() + bt1 = styleSheet['BodyText'] + story = self.story + + # Use only the first line of the class' doc string -- + # no matter how long! (Do the same later for methods) + classDoc = reduceDocStringLength(doc) + + tsa = tableStyleAttributes = [] + + # Make table with class and method rows + # and add it to the story. + p = Paragraph('%s' % self.classCompartment, bt) + p.style.alignment = TA_CENTER + rows = [(p,)] + # No doc strings, now... + # rows = rows + [(Paragraph('%s' % classDoc, bt1),)] + lenRows = len(rows) + tsa.append(('BOX', (0,0), (-1,lenRows-1), 0.25, colors.black)) + for name, doc, sig in self.methodCompartment: + nameAndSig = Paragraph('%s%s' % (name, sig), bt1) + rows.append((nameAndSig,)) + # No doc strings, now... + # docStr = Paragraph('%s' % reduceDocStringLength(doc), bt1) + # rows.append((docStr,)) + tsa.append(('BOX', (0,lenRows), (-1,-1), 0.25, colors.black)) + t = Table(rows, (12*cm,)) + tableStyle = TableStyle(tableStyleAttributes) + t.setStyle(tableStyle) + self.story.append(t) + self.story.append(Spacer(1*cm, 1*cm)) + + + def beginMethod(self, name, doc, sig): + self.methodCompartment.append((name, doc, sig)) + + + def beginFunctions(self, names): + h1, h2, h3, bt = self.h1, self.h2, self.h3, self.bt + if names: + self.story.append(Paragraph('Functions', h2)) + self.classCompartment = chr(171) + ' Module-Level Functions ' + chr(187) + self.methodCompartment = [] + + + def beginFunction(self, name, doc, sig): + self.methodCompartment.append((name, doc, sig)) + + + def endFunctions(self, names): + h1, h2, h3, bt, code = self.h1, self.h2, self.h3, self.bt, self.code + styleSheet = getSampleStyleSheet() + bt1 = styleSheet['BodyText'] + story = self.story + if not names: + return + + tsa = tableStyleAttributes = [] + + # Make table with class and method rows + # and add it to the story. + p = Paragraph('%s' % self.classCompartment, bt) + p.style.alignment = TA_CENTER + rows = [(p,)] + lenRows = len(rows) + tsa.append(('BOX', (0,0), (-1,lenRows-1), 0.25, colors.black)) + for name, doc, sig in self.methodCompartment: + nameAndSig = Paragraph('%s%s' % (name, sig), bt1) + rows.append((nameAndSig,)) + # No doc strings, now... + # docStr = Paragraph('%s' % reduceDocStringLength(doc), bt1) + # rows.append((docStr,)) + tsa.append(('BOX', (0,lenRows), (-1,-1), 0.25, colors.black)) + t = Table(rows, (12*cm,)) + tableStyle = TableStyle(tableStyleAttributes) + t.setStyle(tableStyle) + self.story.append(t) + self.story.append(Spacer(1*cm, 1*cm)) + + +#################################################################### +# +# Main +# +#################################################################### + +def printUsage(): + """docpy.py - Automated documentation for Python source code. + +Usage: python docpy.py [options] + + [options] + -h Print this help message. + + -f name Use the document builder indicated by 'name', + e.g. Ascii, Html, Pdf (default), UmlPdf. + + -m module Generate document for module named 'module' + (default is 'docpy'). + 'module' may follow any of these forms: + - docpy.py + - docpy + - c:\\test\\docpy + and can be any of these: + - standard Python modules + - modules in the Python search path + - modules in the current directory + + -p package Generate document for package named 'package'. + 'package' may follow any of these forms: + - reportlab + - reportlab.platypus + - c:\\test\\reportlab + and can be any of these: + - standard Python packages (?) + - packages in the Python search path + - packages in the current directory + + -s Silent mode (default is unset). + +Examples: + + python docpy.py -h + python docpy.py -m docpy.py -f Ascii + python docpy.py -m string -f Html + python docpy.py -m signsandsymbols.py -f Pdf + python docpy.py -p reportlab.platypus -f UmlPdf + python docpy.py -p reportlab.lib -s -f UmlPdf +""" + + +def documentModule0(pathOrName, builder, opts={}): + """Generate documentation for one Python file in some format. + + This handles Python standard modules like string, custom modules + on the Python search path like e.g. docpy as well as modules + specified with their full path like C:/tmp/junk.py. + + The doc file will always be saved in the current directory with + a basename equal to that of the module, e.g. docpy. + """ + + cwd = os.getcwd() + + # Append directory to Python search path if we get one. + dirName = os.path.dirname(pathOrName) + if dirName: + sys.path.append(dirName) + + # Remove .py extension from module name. + if pathOrName[-3:] == '.py': + modname = pathOrName[:-3] + else: + modname = pathOrName + + # Remove directory paths from module name. + if dirName: + modname = os.path.basename(modname) + + # Load the module. + try: + module = __import__(modname) + except: + print 'Failed to import %s.' % modname + os.chdir(cwd) + return + + # Do the real documentation work. + s = ModuleSkeleton0() + s.inspect(module) + builder.write(s) + + # Remove appended directory from Python search path if we got one. + if dirName: + del sys.path[-1] + + os.chdir(cwd) + + +def _packageWalkCallback((builder, opts), dirPath, files): + "A callback function used when waking over a package tree." + + # Skip __init__ files. + files = filter(lambda f:f != '__init__.py', files) + + files = filter(lambda f:f[-3:] == '.py', files) + for f in files: + path = os.path.join(dirPath, f) + if not opts.get('isSilent', 0): + print path + builder.indentLevel = builder.indentLevel + 1 + documentModule0(path, builder) + builder.indentLevel = builder.indentLevel - 1 + + +def documentPackage0(pathOrName, builder, opts={}): + """Generate documentation for one Python package in some format. + + 'pathOrName' can be either a filesystem path leading to a Python + package or package name whose path will be resolved by importing + the top-level module. + + The doc file will always be saved in the current directory with + a basename equal to that of the package, e.g. reportlab.lib. + """ + + # Did we get a package path with OS-dependant seperators...? + if os.sep in pathOrName: + path = pathOrName + name = os.path.splitext(os.path.basename(path))[0] + # ... or rather a package name? + else: + name = pathOrName + package = __import__(name) + # Some special care needed for dotted names. + if '.' in name: + subname = 'package' + name[find(name, '.'):] + package = eval(subname) + path = os.path.dirname(package.__file__) + + cwd = os.getcwd() + builder.beginPackage(name) + os.path.walk(path, _packageWalkCallback, (builder, opts)) + builder.endPackage(name) + os.chdir(cwd) + + +def main(): + "Handle command-line options and trigger corresponding action." + + opts, args = getopt.getopt(sys.argv[1:], 'hsf:m:p:') + + # Make an options dictionary that is easier to use. + optsDict = {} + for k, v in opts: + optsDict[k] = v + hasOpt = optsDict.has_key + + # On -h print usage and exit immediately. + if hasOpt('-h'): + print printUsage.__doc__ + sys.exit(0) + + # On -s set silent mode. + isSilent = hasOpt('-s') + + # On -f set the appropriate DocBuilder to use or a default one. + builderClassName = optsDict.get('-f', 'Pdf') + 'DocBuilder0' + builder = eval(builderClassName + '()') + + # Set default module or package to document. + if not hasOpt('-p') and not hasOpt('-m'): + optsDict['-m'] = 'docpy' + + # Save a few options for further use. + options = {'isSilent':isSilent} + + # Now call the real documentation functions. + if hasOpt('-m'): + nameOrPath = optsDict['-m'] + if not isSilent: + print "Generating documentation for module %s..." % nameOrPath + builder.begin(name=nameOrPath, typ='module') + documentModule0(nameOrPath, builder, options) + elif hasOpt('-p'): + nameOrPath = optsDict['-p'] + if not isSilent: + print "Generating documentation for package %s..." % nameOrPath + builder.begin(name=nameOrPath, typ='package') + documentPackage0(nameOrPath, builder, options) + builder.end() + + if not isSilent: + print "Saved %s." % builder.outPath + + +if __name__ == '__main__': + main() diff --git a/bin/reportlab/tools/docco/examples.py b/bin/reportlab/tools/docco/examples.py new file mode 100644 index 00000000000..a4eeec54d07 --- /dev/null +++ b/bin/reportlab/tools/docco/examples.py @@ -0,0 +1,851 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/examples.py +import string + +testannotations=""" +def annotations(canvas): + from reportlab.lib.units import inch + canvas.drawString(inch, 2.5*inch, + "setAuthor, setTitle, setSubject have no visible effect") + canvas.drawString(inch, inch, "But if you are viewing this document dynamically") + canvas.drawString(inch, 0.5*inch, "please look at File/Document Info") + canvas.setAuthor("the ReportLab Team") + canvas.setTitle("ReportLab PDF Generation User Guide") + canvas.setSubject("How to Generate PDF files using the ReportLab modules") +""" + +# magic function making module + +test1 = """ +def f(a,b): + print "it worked", a, b + return a+b +""" + +test2 = """ +def g(n): + if n==0: return 1 + else: return n*g(n-1) + """ + +testhello = """ +def hello(c): + from reportlab.lib.units import inch + # move the origin up and to the left + c.translate(inch,inch) + # define a large font + c.setFont("Helvetica", 14) + # choose some colors + c.setStrokeColorRGB(0.2,0.5,0.3) + c.setFillColorRGB(1,0,1) + # draw some lines + c.line(0,0,0,1.7*inch) + c.line(0,0,1*inch,0) + # draw a rectangle + c.rect(0.2*inch,0.2*inch,1*inch,1.5*inch, fill=1) + # make text go straight up + c.rotate(90) + # change color + c.setFillColorRGB(0,0,0.77) + # say hello (note after rotate the y coord needs to be negative!) + c.drawString(0.3*inch, -inch, "Hello World") +""" + +testcoords = """ +def coords(canvas): + from reportlab.lib.units import inch + from reportlab.lib.colors import pink, black, red, blue, green + c = canvas + c.setStrokeColor(pink) + c.grid([inch, 2*inch, 3*inch, 4*inch], [0.5*inch, inch, 1.5*inch, 2*inch, 2.5*inch]) + c.setStrokeColor(black) + c.setFont("Times-Roman", 20) + c.drawString(0,0, "(0,0) the Origin") + c.drawString(2.5*inch, inch, "(2.5,1) in inches") + c.drawString(4*inch, 2.5*inch, "(4, 2.5)") + c.setFillColor(red) + c.rect(0,2*inch,0.2*inch,0.3*inch, fill=1) + c.setFillColor(green) + c.circle(4.5*inch, 0.4*inch, 0.2*inch, fill=1) +""" + +testtranslate = """ +def translate(canvas): + from reportlab.lib.units import cm + canvas.translate(2.3*cm, 0.3*cm) + coords(canvas) + """ + +testscale = """ +def scale(canvas): + canvas.scale(0.75, 0.5) + coords(canvas) +""" + +testscaletranslate = """ +def scaletranslate(canvas): + from reportlab.lib.units import inch + canvas.setFont("Courier-BoldOblique", 12) + # save the state + canvas.saveState() + # scale then translate + canvas.scale(0.3, 0.5) + canvas.translate(2.4*inch, 1.5*inch) + canvas.drawString(0, 2.7*inch, "Scale then translate") + coords(canvas) + # forget the scale and translate... + canvas.restoreState() + # translate then scale + canvas.translate(2.4*inch, 1.5*inch) + canvas.scale(0.3, 0.5) + canvas.drawString(0, 2.7*inch, "Translate then scale") + coords(canvas) +""" + +testmirror = """ +def mirror(canvas): + from reportlab.lib.units import inch + canvas.translate(5.5*inch, 0) + canvas.scale(-1.0, 1.0) + coords(canvas) +""" + +testcolors = """ +def colors(canvas): + from reportlab.lib import colors + from reportlab.lib.units import inch + black = colors.black + y = x = 0; dy=inch*3/4.0; dx=inch*5.5/5; w=h=dy/2; rdx=(dx-w)/2 + rdy=h/5.0; texty=h+2*rdy + canvas.setFont("Helvetica",10) + for [namedcolor, name] in ( + [colors.lavenderblush, "lavenderblush"], + [colors.lawngreen, "lawngreen"], + [colors.lemonchiffon, "lemonchiffon"], + [colors.lightblue, "lightblue"], + [colors.lightcoral, "lightcoral"]): + canvas.setFillColor(namedcolor) + canvas.rect(x+rdx, y+rdy, w, h, fill=1) + canvas.setFillColor(black) + canvas.drawCentredString(x+dx/2, y+texty, name) + x = x+dx + y = y + dy; x = 0 + for rgb in [(1,0,0), (0,1,0), (0,0,1), (0.5,0.3,0.1), (0.4,0.5,0.3)]: + r,g,b = rgb + canvas.setFillColorRGB(r,g,b) + canvas.rect(x+rdx, y+rdy, w, h, fill=1) + canvas.setFillColor(black) + canvas.drawCentredString(x+dx/2, y+texty, "r%s g%s b%s"%rgb) + x = x+dx + y = y + dy; x = 0 + for cmyk in [(1,0,0,0), (0,1,0,0), (0,0,1,0), (0,0,0,1), (0,0,0,0)]: + c,m,y1,k = cmyk + canvas.setFillColorCMYK(c,m,y1,k) + canvas.rect(x+rdx, y+rdy, w, h, fill=1) + canvas.setFillColor(black) + canvas.drawCentredString(x+dx/2, y+texty, "c%s m%s y%s k%s"%cmyk) + x = x+dx + y = y + dy; x = 0 + for gray in (0.0, 0.25, 0.50, 0.75, 1.0): + canvas.setFillGray(gray) + canvas.rect(x+rdx, y+rdy, w, h, fill=1) + canvas.setFillColor(black) + canvas.drawCentredString(x+dx/2, y+texty, "gray: %s"%gray) + x = x+dx +""" + +testspumoni = """ +def spumoni(canvas): + from reportlab.lib.units import inch + from reportlab.lib.colors import pink, green, brown, white + x = 0; dx = 0.4*inch + for i in range(4): + for color in (pink, green, brown): + canvas.setFillColor(color) + canvas.rect(x,0,dx,3*inch,stroke=0,fill=1) + x = x+dx + canvas.setFillColor(white) + canvas.setStrokeColor(white) + canvas.setFont("Helvetica-Bold", 85) + canvas.drawCentredString(2.75*inch, 1.3*inch, "SPUMONI") +""" + +testspumoni2 = """ +def spumoni2(canvas): + from reportlab.lib.units import inch + from reportlab.lib.colors import pink, green, brown, white, black + # draw the previous drawing + spumoni(canvas) + # now put an ice cream cone on top of it: + # first draw a triangle (ice cream cone) + p = canvas.beginPath() + xcenter = 2.75*inch + radius = 0.45*inch + p.moveTo(xcenter-radius, 1.5*inch) + p.lineTo(xcenter+radius, 1.5*inch) + p.lineTo(xcenter, 0) + canvas.setFillColor(brown) + canvas.setStrokeColor(black) + canvas.drawPath(p, fill=1) + # draw some circles (scoops) + y = 1.5*inch + for color in (pink, green, brown): + canvas.setFillColor(color) + canvas.circle(xcenter, y, radius, fill=1) + y = y+radius +""" + +testbezier = """ +def bezier(canvas): + from reportlab.lib.colors import yellow, green, red, black + from reportlab.lib.units import inch + i = inch + d = i/4 + # define the bezier curve control points + x1,y1, x2,y2, x3,y3, x4,y4 = d,1.5*i, 1.5*i,d, 3*i,d, 5.5*i-d,3*i-d + # draw a figure enclosing the control points + canvas.setFillColor(yellow) + p = canvas.beginPath() + p.moveTo(x1,y1) + for (x,y) in [(x2,y2), (x3,y3), (x4,y4)]: + p.lineTo(x,y) + canvas.drawPath(p, fill=1, stroke=0) + # draw the tangent lines + canvas.setLineWidth(inch*0.1) + canvas.setStrokeColor(green) + canvas.line(x1,y1,x2,y2) + canvas.setStrokeColor(red) + canvas.line(x3,y3,x4,y4) + # finally draw the curve + canvas.setStrokeColor(black) + canvas.bezier(x1,y1, x2,y2, x3,y3, x4,y4) +""" + +testbezier2 = """ +def bezier2(canvas): + from reportlab.lib.colors import yellow, green, red, black + from reportlab.lib.units import inch + # make a sequence of control points + xd,yd = 5.5*inch/2, 3*inch/2 + xc,yc = xd,yd + dxdy = [(0,0.33), (0.33,0.33), (0.75,1), (0.875,0.875), + (0.875,0.875), (1,0.75), (0.33,0.33), (0.33,0)] + pointlist = [] + for xoffset in (1,-1): + yoffset = xoffset + for (dx,dy) in dxdy: + px = xc + xd*xoffset*dx + py = yc + yd*yoffset*dy + pointlist.append((px,py)) + yoffset = -xoffset + for (dy,dx) in dxdy: + px = xc + xd*xoffset*dx + py = yc + yd*yoffset*dy + pointlist.append((px,py)) + # draw tangent lines and curves + canvas.setLineWidth(inch*0.1) + while pointlist: + [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] = pointlist[:4] + del pointlist[:4] + canvas.setLineWidth(inch*0.1) + canvas.setStrokeColor(green) + canvas.line(x1,y1,x2,y2) + canvas.setStrokeColor(red) + canvas.line(x3,y3,x4,y4) + # finally draw the curve + canvas.setStrokeColor(black) + canvas.bezier(x1,y1, x2,y2, x3,y3, x4,y4) +""" + +testpencil = """ +def pencil(canvas, text="No.2"): + from reportlab.lib.colors import yellow, red, black,white + from reportlab.lib.units import inch + u = inch/10.0 + canvas.setStrokeColor(black) + canvas.setLineWidth(4) + # draw erasor + canvas.setFillColor(red) + canvas.circle(30*u, 5*u, 5*u, stroke=1, fill=1) + # draw all else but the tip (mainly rectangles with different fills) + canvas.setFillColor(yellow) + canvas.rect(10*u,0,20*u,10*u, stroke=1, fill=1) + canvas.setFillColor(black) + canvas.rect(23*u,0,8*u,10*u,fill=1) + canvas.roundRect(14*u, 3.5*u, 8*u, 3*u, 1.5*u, stroke=1, fill=1) + canvas.setFillColor(white) + canvas.rect(25*u,u,1.2*u,8*u, fill=1,stroke=0) + canvas.rect(27.5*u,u,1.2*u,8*u, fill=1, stroke=0) + canvas.setFont("Times-Roman", 3*u) + canvas.drawCentredString(18*u, 4*u, text) + # now draw the tip + penciltip(canvas,debug=0) + # draw broken lines across the body. + canvas.setDash([10,5,16,10],0) + canvas.line(11*u,2.5*u,22*u,2.5*u) + canvas.line(22*u,7.5*u,12*u,7.5*u) + """ + +testpenciltip = """ +def penciltip(canvas, debug=1): + from reportlab.lib.colors import tan, black, green + from reportlab.lib.units import inch + u = inch/10.0 + canvas.setLineWidth(4) + if debug: + canvas.scale(2.8,2.8) # make it big + canvas.setLineWidth(1) # small lines + canvas.setStrokeColor(black) + canvas.setFillColor(tan) + p = canvas.beginPath() + p.moveTo(10*u,0) + p.lineTo(0,5*u) + p.lineTo(10*u,10*u) + p.curveTo(11.5*u,10*u, 11.5*u,7.5*u, 10*u,7.5*u) + p.curveTo(12*u,7.5*u, 11*u,2.5*u, 9.7*u,2.5*u) + p.curveTo(10.5*u,2.5*u, 11*u,0, 10*u,0) + canvas.drawPath(p, stroke=1, fill=1) + canvas.setFillColor(black) + p = canvas.beginPath() + p.moveTo(0,5*u) + p.lineTo(4*u,3*u) + p.lineTo(5*u,4.5*u) + p.lineTo(3*u,6.5*u) + canvas.drawPath(p, stroke=1, fill=1) + if debug: + canvas.setStrokeColor(green) # put in a frame of reference + canvas.grid([0,5*u,10*u,15*u], [0,5*u,10*u]) +""" + +testnoteannotation = """ +from reportlab.platypus.flowables import Flowable +class NoteAnnotation(Flowable): + '''put a pencil in the margin.''' + def wrap(self, *args): + return (1,10) # I take up very little space! (?) + def draw(self): + canvas = self.canv + canvas.translate(-10,-10) + canvas.rotate(180) + canvas.scale(0.2,0.2) + pencil(canvas, text="NOTE") +""" + +testhandannotation = """ +from reportlab.platypus.flowables import Flowable +from reportlab.lib.colors import tan, green +class HandAnnotation(Flowable): + '''A hand flowable.''' + def __init__(self, xoffset=0, size=None, fillcolor=tan, strokecolor=green): + from reportlab.lib.units import inch + if size is None: size=4*inch + self.fillcolor, self.strokecolor = fillcolor, strokecolor + self.xoffset = xoffset + self.size = size + # normal size is 4 inches + self.scale = size/(4.0*inch) + def wrap(self, *args): + return (self.xoffset, self.size) + def draw(self): + canvas = self.canv + canvas.setLineWidth(6) + canvas.setFillColor(self.fillcolor) + canvas.setStrokeColor(self.strokecolor) + canvas.translate(self.xoffset+self.size,0) + canvas.rotate(90) + canvas.scale(self.scale, self.scale) + hand(canvas, debug=0, fill=1) +""" + +lyrics = '''\ +well she hit Net Solutions +and she registered her own .com site now +and filled it up with yahoo profile pics +she snarfed in one night now +and she made 50 million when Hugh Hefner +bought up the rights now +and she'll have fun fun fun +til her Daddy takes the keyboard away''' + +lyrics = string.split(lyrics, "\n") +testtextsize = """ +def textsize(canvas): + from reportlab.lib.units import inch + from reportlab.lib.colors import magenta, red + canvas.setFont("Times-Roman", 20) + canvas.setFillColor(red) + canvas.drawCentredString(2.75*inch, 2.5*inch, "Font size examples") + canvas.setFillColor(magenta) + size = 7 + y = 2.3*inch + x = 1.3*inch + for line in lyrics: + canvas.setFont("Helvetica", size) + canvas.drawRightString(x,y,"%s points: " % size) + canvas.drawString(x,y, line) + y = y-size*1.2 + size = size+1.5 +""" + +teststar = """ +def star(canvas, title="Title Here", aka="Comment here.", + xcenter=None, ycenter=None, nvertices=5): + from math import pi + from reportlab.lib.units import inch + radius=inch/3.0 + if xcenter is None: xcenter=2.75*inch + if ycenter is None: ycenter=1.5*inch + canvas.drawCentredString(xcenter, ycenter+1.3*radius, title) + canvas.drawCentredString(xcenter, ycenter-1.4*radius, aka) + p = canvas.beginPath() + p.moveTo(xcenter,ycenter+radius) + from math import pi, cos, sin + angle = (2*pi)*2/5.0 + startangle = pi/2.0 + for vertex in range(nvertices-1): + nextangle = angle*(vertex+1)+startangle + x = xcenter + radius*cos(nextangle) + y = ycenter + radius*sin(nextangle) + p.lineTo(x,y) + if nvertices==5: + p.close() + canvas.drawPath(p) +""" + +testjoins = """ +def joins(canvas): + from reportlab.lib.units import inch + # make lines big + canvas.setLineWidth(5) + star(canvas, "Default: mitered join", "0: pointed", xcenter = 1*inch) + canvas.setLineJoin(1) + star(canvas, "Round join", "1: rounded") + canvas.setLineJoin(2) + star(canvas, "Bevelled join", "2: square", xcenter=4.5*inch) +""" + +testcaps = """ +def caps(canvas): + from reportlab.lib.units import inch + # make lines big + canvas.setLineWidth(5) + star(canvas, "Default", "no projection",xcenter = 1*inch, + nvertices=4) + canvas.setLineCap(1) + star(canvas, "Round cap", "1: ends in half circle", nvertices=4) + canvas.setLineCap(2) + star(canvas, "Square cap", "2: projects out half a width", xcenter=4.5*inch, + nvertices=4) +""" + +testdashes = """ +def dashes(canvas): + from reportlab.lib.units import inch + # make lines big + canvas.setDash(6,3) + star(canvas, "Simple dashes", "6 points on, 3 off", xcenter = 1*inch) + canvas.setDash(1,2) + star(canvas, "Dots", "One on, two off") + canvas.setDash([1,1,3,3,1,4,4,1], 0) + star(canvas, "Complex Pattern", "[1,1,3,3,1,4,4,1]", xcenter=4.5*inch) +""" + +testcustomfont1 = """ +def customfont1(canvas): + # we know some glyphs are missing, suppress warnings + import reportlab.rl_config + reportlab.rl_config.warnOnMissingFontGlyphs = 0 + + import rl_doc_utils + from reportlab.pdfbase import pdfmetrics + afmFile, pfbFile = rl_doc_utils.getJustFontPaths() + justFace = pdfmetrics.EmbeddedType1Face(afmFile, pfbFile) + faceName = 'LettErrorRobot-Chrome' # pulled from AFM file + pdfmetrics.registerTypeFace(justFace) + justFont = pdfmetrics.Font('LettErrorRobot-Chrome', + faceName, + 'WinAnsiEncoding') + pdfmetrics.registerFont(justFont) + + canvas.setFont('LettErrorRobot-Chrome', 32) + canvas.drawString(10, 150, 'This should be in') + canvas.drawString(10, 100, 'LettErrorRobot-Chrome') +""" + +testttffont1 = """ +def ttffont1(canvas): + # we know some glyphs are missing, suppress warnings + import reportlab.rl_config + reportlab.rl_config.warnOnMissingFontGlyphs = 0 + from reportlab.pdfbase import pdfmetrics + from reportlab.pdfbase.ttfonts import TTFont + pdfmetrics.registerFont(TTFont('Rina', 'rina.ttf')) + from reportlab.pdfgen.canvas import Canvas + + canvas.setFont('Rina', 32) + canvas.drawString(10, 150, "Some UTF-8 text encoded") + canvas.drawString(10, 100, "in the Rina TT Font!") +""" + +testcursormoves1 = """ +def cursormoves1(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(inch, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 14) + for line in lyrics: + textobject.textLine(line) + textobject.setFillGray(0.4) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" + +testcursormoves2 = """ +def cursormoves2(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(2, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 14) + for line in lyrics: + textobject.textOut(line) + textobject.moveCursor(14,14) # POSITIVE Y moves down!!! + textobject.setFillColorRGB(0.4,0,1) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" + +testcharspace = """ +def charspace(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(3, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 10) + charspace = 0 + for line in lyrics: + textobject.setCharSpace(charspace) + textobject.textLine("%s: %s" %(charspace,line)) + charspace = charspace+0.5 + textobject.setFillGray(0.4) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" + +testwordspace = """ +def wordspace(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(3, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 12) + wordspace = 0 + for line in lyrics: + textobject.setWordSpace(wordspace) + textobject.textLine("%s: %s" %(wordspace,line)) + wordspace = wordspace+2.5 + textobject.setFillColorCMYK(0.4,0,0.4,0.2) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" +testhorizontalscale = """ +def horizontalscale(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(3, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 12) + horizontalscale = 80 # 100 is default + for line in lyrics: + textobject.setHorizScale(horizontalscale) + textobject.textLine("%s: %s" %(horizontalscale,line)) + horizontalscale = horizontalscale+10 + textobject.setFillColorCMYK(0.0,0.4,0.4,0.2) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" +testleading = """ +def leading(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(3, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 14) + leading = 8 + for line in lyrics: + textobject.setLeading(leading) + textobject.textLine("%s: %s" %(leading,line)) + leading = leading+2.5 + textobject.setFillColorCMYK(0.8,0,0,0.3) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" + +testhand = """ +def hand(canvas, debug=1, fill=0): + (startx, starty) = (0,0) + curves = [ + ( 0, 2), ( 0, 4), ( 0, 8), # back of hand + ( 5, 8), ( 7,10), ( 7,14), + (10,14), (10,13), ( 7.5, 8), # thumb + (13, 8), (14, 8), (17, 8), + (19, 8), (19, 6), (17, 6), + (15, 6), (13, 6), (11, 6), # index, pointing + (12, 6), (13, 6), (14, 6), + (16, 6), (16, 4), (14, 4), + (13, 4), (12, 4), (11, 4), # middle + (11.5, 4), (12, 4), (13, 4), + (15, 4), (15, 2), (13, 2), + (12.5, 2), (11.5, 2), (11, 2), # ring + (11.5, 2), (12, 2), (12.5, 2), + (14, 2), (14, 0), (12.5, 0), + (10, 0), (8, 0), (6, 0), # pinky, then close + ] + from reportlab.lib.units import inch + if debug: canvas.setLineWidth(6) + u = inch*0.2 + p = canvas.beginPath() + p.moveTo(startx, starty) + ccopy = list(curves) + while ccopy: + [(x1,y1), (x2,y2), (x3,y3)] = ccopy[:3] + del ccopy[:3] + p.curveTo(x1*u,y1*u,x2*u,y2*u,x3*u,y3*u) + p.close() + canvas.drawPath(p, fill=fill) + if debug: + from reportlab.lib.colors import red, green + (lastx, lasty) = (startx, starty) + ccopy = list(curves) + while ccopy: + [(x1,y1), (x2,y2), (x3,y3)] = ccopy[:3] + del ccopy[:3] + canvas.setStrokeColor(red) + canvas.line(lastx*u,lasty*u, x1*u,y1*u) + canvas.setStrokeColor(green) + canvas.line(x2*u,y2*u, x3*u,y3*u) + (lastx,lasty) = (x3,y3) +""" + +testhand2 = """ +def hand2(canvas): + canvas.translate(20,10) + canvas.setLineWidth(3) + canvas.setFillColorRGB(0.1, 0.3, 0.9) + canvas.setStrokeGray(0.5) + hand(canvas, debug=0, fill=1) +""" + +testfonts = """ +def fonts(canvas): + from reportlab.lib.units import inch + text = "Now is the time for all good men to..." + x = 1.8*inch + y = 2.7*inch + for font in canvas.getAvailableFonts(): + canvas.setFont(font, 10) + canvas.drawString(x,y,text) + canvas.setFont("Helvetica", 10) + canvas.drawRightString(x-10,y, font+":") + y = y-13 +""" + +testarcs = """ +def arcs(canvas): + from reportlab.lib.units import inch + canvas.setLineWidth(4) + canvas.setStrokeColorRGB(0.8, 1, 0.6) + # draw rectangles enclosing the arcs + canvas.rect(inch, inch, 1.5*inch, inch) + canvas.rect(3*inch, inch, inch, 1.5*inch) + canvas.setStrokeColorRGB(0, 0.2, 0.4) + canvas.setFillColorRGB(1, 0.6, 0.8) + p = canvas.beginPath() + p.moveTo(0.2*inch, 0.2*inch) + p.arcTo(inch, inch, 2.5*inch,2*inch, startAng=-30, extent=135) + p.arc(3*inch, inch, 4*inch, 2.5*inch, startAng=-45, extent=270) + canvas.drawPath(p, fill=1, stroke=1) +""" +testvariousshapes = """ +def variousshapes(canvas): + from reportlab.lib.units import inch + inch = int(inch) + canvas.setStrokeGray(0.5) + canvas.grid(range(0,11*inch/2,inch/2), range(0,7*inch/2,inch/2)) + canvas.setLineWidth(4) + canvas.setStrokeColorRGB(0, 0.2, 0.7) + canvas.setFillColorRGB(1, 0.6, 0.8) + p = canvas.beginPath() + p.rect(0.5*inch, 0.5*inch, 0.5*inch, 2*inch) + p.circle(2.75*inch, 1.5*inch, 0.3*inch) + p.ellipse(3.5*inch, 0.5*inch, 1.2*inch, 2*inch) + canvas.drawPath(p, fill=1, stroke=1) +""" + +testclosingfigures = """ +def closingfigures(canvas): + from reportlab.lib.units import inch + h = inch/3.0; k = inch/2.0 + canvas.setStrokeColorRGB(0.2,0.3,0.5) + canvas.setFillColorRGB(0.8,0.6,0.2) + canvas.setLineWidth(4) + p = canvas.beginPath() + for i in (1,2,3,4): + for j in (1,2): + xc,yc = inch*i, inch*j + p.moveTo(xc,yc) + p.arcTo(xc-h, yc-k, xc+h, yc+k, startAng=0, extent=60*i) + # close only the first one, not the second one + if j==1: + p.close() + canvas.drawPath(p, fill=1, stroke=1) +""" + +testforms = """ +def forms(canvas): + #first create a form... + canvas.beginForm("SpumoniForm") + #re-use some drawing functions from earlier + spumoni(canvas) + canvas.endForm() + + #then draw it + canvas.doForm("SpumoniForm") +""" + +def doctemplateillustration(canvas): + from reportlab.lib.units import inch + canvas.setFont("Helvetica", 10) + canvas.drawString(inch/4.0, 2.75*inch, "DocTemplate") + W = 4/3.0*inch + H = 2*inch + Wd = x = inch/4.0 + Hd =y = inch/2.0 + for name in ("two column", "chapter page", "title page"): + canvas.setFillColorRGB(0.5,1.0,1.0) + canvas.rect(x,y,W,H, fill=1) + canvas.setFillColorRGB(0,0,0) + canvas.drawString(x+inch/8, y+H-Wd, "PageTemplate") + canvas.drawCentredString(x+W/2.0, y-Wd, name) + x = x+W+Wd + canvas.saveState() + d = inch/16 + dW = (W-3*d)/2.0 + hD = H -2*d-Wd + canvas.translate(Wd+d, Hd+d) + for name in ("left Frame", "right Frame"): + canvas.setFillColorRGB(1.0,0.5,1.0) + canvas.rect(0,0, dW,hD, fill=1) + canvas.setFillGray(0.7) + dd= d/2.0 + ddH = (hD-6*dd)/5.0 + ddW = dW-2*dd + yy = dd + xx = dd + for i in range(5): + canvas.rect(xx,yy,ddW,ddH, fill=1, stroke=0) + yy = yy+ddH+dd + canvas.setFillColorRGB(0,0,0) + canvas.saveState() + canvas.rotate(90) + canvas.drawString(d,-dW/2, name) + canvas.restoreState() + canvas.translate(dW+d,0) + canvas.restoreState() + canvas.setFillColorRGB(1.0, 0.5, 1.0) + mx = Wd+W+Wd+d + my = Hd+d + mW = W-2*d + mH = H-d-Hd + canvas.rect(mx, my, mW, mH, fill=1) + canvas.rect(Wd+2*(W+Wd)+d, Hd+3*d, W-2*d, H/2.0, fill=1) + canvas.setFillGray(0.7) + canvas.rect(Wd+2*(W+Wd)+d+dd, Hd+5*d, W-2*d-2*dd, H/2.0-2*d-dd, fill=1) + xx = mx+dd + yy = my+mH/5.0 + ddH = (mH-6*dd-mH/5.0)/3.0 + ddW = mW - 2*dd + for i in range(3): + canvas.setFillGray(0.7) + canvas.rect(xx,yy,ddW,ddH, fill=1, stroke=1) + canvas.setFillGray(0) + canvas.drawString(xx+dd/2.0,yy+dd/2.0, "flowable %s" %(157-i)) + yy = yy+ddH+dd + canvas.drawCentredString(3*Wd+2*W+W/2, Hd+H/2.0, "First Flowable") + canvas.setFont("Times-BoldItalic", 8) + canvas.setFillGray(0) + canvas.drawCentredString(mx+mW/2.0, my+mH+3*dd, "Chapter 6: Lubricants") + canvas.setFont("Times-BoldItalic", 10) + canvas.drawCentredString(3*Wd+2*W+W/2, Hd+H-H/4, "College Life") + +# D = dir() +g = globals() +Dprime = {} +from types import StringType +from string import strip +for (a,b) in g.items(): + if a[:4]=="test" and type(b) is StringType: + #print 'for', a + #print b + b = strip(b) + exec(b+'\n') + +platypussetup = """ +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer +from reportlab.lib.styles import getSampleStyleSheet +from reportlab.rl_config import defaultPageSize +from reportlab.lib.units import inch +PAGE_HEIGHT=defaultPageSize[1]; PAGE_WIDTH=defaultPageSize[0] +styles = getSampleStyleSheet() +""" +platypusfirstpage = """ +Title = "Hello world" +pageinfo = "platypus example" +def myFirstPage(canvas, doc): + canvas.saveState() + canvas.setFont('Times-Bold',16) + canvas.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, Title) + canvas.setFont('Times-Roman',9) + canvas.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo) + canvas.restoreState() +""" +platypusnextpage = """ +def myLaterPages(canvas, doc): + canvas.saveState() + canvas.setFont('Times-Roman',9) + canvas.drawString(inch, 0.75 * inch, "Page %d %s" % (doc.page, pageinfo)) + canvas.restoreState() +""" +platypusgo = """ +def go(): + doc = SimpleDocTemplate("phello.pdf") + Story = [Spacer(1,2*inch)] + style = styles["Normal"] + for i in range(100): + bogustext = ("This is Paragraph number %s. " % i) *20 + p = Paragraph(bogustext, style) + Story.append(p) + Story.append(Spacer(1,0.2*inch)) + doc.build(Story, onFirstPage=myFirstPage, onLaterPages=myLaterPages) +""" + +if __name__=="__main__": + # then do the platypus hello world + for b in platypussetup, platypusfirstpage, platypusnextpage, platypusgo: + b = strip(b) + exec(b+'\n') + go() diff --git a/bin/reportlab/tools/docco/graphdocpy.py b/bin/reportlab/tools/docco/graphdocpy.py new file mode 100644 index 00000000000..29aecf16c6c --- /dev/null +++ b/bin/reportlab/tools/docco/graphdocpy.py @@ -0,0 +1,980 @@ +#!/usr/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/graphdocpy.py + +"""Generate documentation for reportlab.graphics classes. + +Type the following for usage info: + + python graphdocpy.py -h +""" + + +__version__ = '0.8' + + +import sys +sys.path.insert(0, '.') +import os, re, types, string, getopt, pickle, copy, time, pprint, traceback +from string import find, join, split, replace, expandtabs, rstrip +import reportlab +from reportlab import rl_config + +from docpy import PackageSkeleton0, ModuleSkeleton0 +from docpy import DocBuilder0, PdfDocBuilder0, HtmlDocBuilder0 +from docpy import htmlescape, htmlrepr, defaultformat, \ + getdoc, reduceDocStringLength +from docpy import makeHtmlSection, makeHtmlSubSection, \ + makeHtmlInlineImage + +from reportlab.lib.units import inch, cm +from reportlab.lib.pagesizes import A4 +from reportlab.lib import colors +from reportlab.lib.enums import TA_CENTER, TA_LEFT +from reportlab.lib.utils import getStringIO +#from StringIO import StringIO +#getStringIO=StringIO +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.pdfgen import canvas +from reportlab.platypus.flowables import Flowable, Spacer +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.tableofcontents import TableOfContents +from reportlab.platypus.flowables \ + import Flowable, Preformatted,Spacer, Image, KeepTogether, PageBreak +from reportlab.platypus.xpreformatted import XPreformatted +from reportlab.platypus.frames import Frame +from reportlab.platypus.doctemplate \ + import PageTemplate, BaseDocTemplate +from reportlab.platypus.tables import TableStyle, Table +from reportlab.graphics.shapes import NotImplementedError +import inspect + +# Needed to draw Widget/Drawing demos. + +from reportlab.graphics.widgetbase import Widget +from reportlab.graphics.shapes import Drawing +from reportlab.graphics import shapes +from reportlab.graphics import renderPDF + +VERBOSE = rl_config.verbose +VERIFY = 1 + +_abstractclasserr_re = re.compile(r'^\s*abstract\s*class\s*(\w+)\s*instantiated',re.I) + +#################################################################### +# +# Stuff needed for building PDF docs. +# +#################################################################### + +def mainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + + pageNumber = canvas.getPageNumber() + canvas.line(2*cm, A4[1]-2*cm, A4[0]-2*cm, A4[1]-2*cm) + canvas.line(2*cm, 2*cm, A4[0]-2*cm, 2*cm) + if pageNumber > 1: + canvas.setFont('Times-Roman', 12) + canvas.drawString(4 * inch, cm, "%d" % pageNumber) + if hasattr(canvas, 'headerLine'): # hackish + headerline = string.join(canvas.headerLine, ' \xc2\x8d ') + canvas.drawString(2*cm, A4[1]-1.75*cm, headerline) + + canvas.setFont('Times-Roman', 8) + msg = "Generated with docpy. See http://www.reportlab.com!" + canvas.drawString(2*cm, 1.65*cm, msg) + + canvas.restoreState() + + +class MyTemplate(BaseDocTemplate): + "The document template used for all PDF documents." + + _invalidInitArgs = ('pageTemplates',) + + def __init__(self, filename, **kw): + frame1 = Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1') + self.allowSplitting = 0 + apply(BaseDocTemplate.__init__, (self, filename), kw) + self.addPageTemplates(PageTemplate('normal', [frame1], mainPageFrame)) + + def afterFlowable(self, flowable): + "Takes care of header line, TOC and outline entries." + + if flowable.__class__.__name__ == 'Paragraph': + f = flowable + + # Build a list of heading parts. + # So far, this is the *last* item on the *previous* page... + if f.style.name[:8] == 'Heading0': + self.canv.headerLine = [f.text] # hackish + elif f.style.name[:8] == 'Heading1': + if len(self.canv.headerLine) == 2: + del self.canv.headerLine[-1] + elif len(self.canv.headerLine) == 3: + del self.canv.headerLine[-1] + del self.canv.headerLine[-1] + self.canv.headerLine.append(f.text) + elif f.style.name[:8] == 'Heading2': + if len(self.canv.headerLine) == 3: + del self.canv.headerLine[-1] + self.canv.headerLine.append(f.text) + + if f.style.name[:7] == 'Heading': + # Register TOC entries. + headLevel = int(f.style.name[7:]) + self.notify('TOCEntry', (headLevel, flowable.getPlainText(), self.page)) + + # Add PDF outline entries. + c = self.canv + title = f.text + key = str(hash(f)) + lev = int(f.style.name[7:]) + try: + if lev == 0: + isClosed = 0 + else: + isClosed = 1 + c.bookmarkPage(key) + c.addOutlineEntry(title, key, level=lev, closed=isClosed) + c.showOutline() + except: + if VERBOSE: + # AR hacking in exception handlers + print 'caught exception in MyTemplate.afterFlowable with heading text %s' % f.text + traceback.print_exc() + else: + pass + + +#################################################################### +# +# Utility functions +# +#################################################################### +def indentLevel(line, spacesPerTab=4): + """Counts the indent levels on the front. + + It is assumed that one tab equals 4 spaces. + """ + + x = 0 + nextTab = 4 + for ch in line: + if ch == ' ': + x = x + 1 + elif ch == '\t': + x = nextTab + nextTab = x + spacesPerTab + else: + return x + + +assert indentLevel('hello') == 0, 'error in indentLevel' +assert indentLevel(' hello') == 1, 'error in indentLevel' +assert indentLevel(' hello') == 2, 'error in indentLevel' +assert indentLevel(' hello') == 3, 'error in indentLevel' +assert indentLevel('\thello') == 4, 'error in indentLevel' +assert indentLevel(' \thello') == 4, 'error in indentLevel' +assert indentLevel('\t hello') == 5, 'error in indentLevel' + +#################################################################### +# +# Special-purpose document builders +# +#################################################################### + +class GraphPdfDocBuilder0(PdfDocBuilder0): + """A PDF document builder displaying widgets and drawings. + + This generates a PDF file where only methods named 'demo' are + listed for any class C. If C happens to be a subclass of Widget + and has a 'demo' method, this method is assumed to generate and + return a sample widget instance, that is then appended graphi- + cally to the Platypus story. + + Something similar happens for functions. If their names start + with 'sample' they are supposed to generate and return a sample + drawing. This is then taken and appended graphically to the + Platypus story, as well. + """ + + fileSuffix = '.pdf' + + def begin(self, name='', typ=''): + styleSheet = getSampleStyleSheet() + self.code = styleSheet['Code'] + self.bt = styleSheet['BodyText'] + self.story = [] + + # Cover page + t = time.gmtime(time.time()) + timeString = time.strftime("%Y-%m-%d %H:%M", t) + self.story.append(Paragraph('Documentation for %s "%s"' % (typ, name), self.bt)) + self.story.append(Paragraph('Generated by: graphdocpy.py version %s' % __version__, self.bt)) + self.story.append(Paragraph('Date generated: %s' % timeString, self.bt)) + self.story.append(Paragraph('Format: PDF', self.bt)) + self.story.append(PageBreak()) + + # Table of contents + toc = TableOfContents() + self.story.append(toc) + self.story.append(PageBreak()) + + + def end(self, fileName=None): + if fileName: # overrides output path + self.outPath = fileName + elif self.packageName: + self.outPath = self.packageName + self.fileSuffix + elif self.skeleton: + self.outPath = self.skeleton.getModuleName() + self.fileSuffix + else: + self.outPath = '' + + if self.outPath: + doc = MyTemplate(self.outPath) + doc.multiBuild(self.story) + + + def beginModule(self, name, doc, imported): + story = self.story + bt = self.bt + + # Defer displaying the module header info to later... + self.shouldDisplayModule = (name, doc, imported) + self.hasDisplayedModule = 0 + + + def endModule(self, name, doc, imported): + if self.hasDisplayedModule: + DocBuilder0.endModule(self, name, doc, imported) + + + def beginClasses(self, names): + # Defer displaying the module header info to later... + if self.shouldDisplayModule: + self.shouldDisplayClasses = names + + + # Skip all methods. + def beginMethod(self, name, doc, sig): + pass + + + def endMethod(self, name, doc, sig): + pass + + + def beginClass(self, name, doc, bases): + "Append a graphic demo of a Widget or Drawing at the end of a class." + + if VERBOSE: + print 'GraphPdfDocBuilder.beginClass(%s...)' % name + + aClass = eval('self.skeleton.moduleSpace.' + name) + if issubclass(aClass, Widget): + if self.shouldDisplayModule: + modName, modDoc, imported = self.shouldDisplayModule + self.story.append(Paragraph(modName, self.makeHeadingStyle(self.indentLevel-2, 'module'))) + self.story.append(XPreformatted(modDoc, self.bt)) + self.shouldDisplayModule = 0 + self.hasDisplayedModule = 1 + if self.shouldDisplayClasses: + self.story.append(Paragraph('Classes', self.makeHeadingStyle(self.indentLevel-1))) + self.shouldDisplayClasses = 0 + PdfDocBuilder0.beginClass(self, name, doc, bases) + self.beginAttributes(aClass) + + elif issubclass(aClass, Drawing): + if self.shouldDisplayModule: + modName, modDoc, imported = self.shouldDisplayModule + self.story.append(Paragraph(modName, self.makeHeadingStyle(self.indentLevel-2, 'module'))) + self.story.append(XPreformatted(modDoc, self.bt)) + self.shouldDisplayModule = 0 + self.hasDisplayedModule = 1 + if self.shouldDisplayClasses: + self.story.append(Paragraph('Classes', self.makeHeadingStyle(self.indentLevel-1))) + self.shouldDisplayClasses = 0 + PdfDocBuilder0.beginClass(self, name, doc, bases) + + + def beginAttributes(self, aClass): + "Append a list of annotated attributes of a class." + + self.story.append(Paragraph( + 'Public Attributes', + self.makeHeadingStyle(self.indentLevel+1))) + + map = aClass._attrMap + if map: + map = map.items() + map.sort() + else: + map = [] + for name, typ in map: + if typ != None: + if hasattr(typ, 'desc'): + desc = typ.desc + else: + desc = '%s' % typ.__class__.__name__ + else: + desc = 'None' + self.story.append(Paragraph( + "%s %s" % (name, desc), self.bt)) + self.story.append(Paragraph("", self.bt)) + + + def endClass(self, name, doc, bases): + "Append a graphic demo of a Widget or Drawing at the end of a class." + + PdfDocBuilder0.endClass(self, name, doc, bases) + + aClass = eval('self.skeleton.moduleSpace.' + name) + if hasattr(aClass, '_nodoc'): + pass + elif issubclass(aClass, Widget): + try: + widget = aClass() + except AssertionError, err: + if _abstractclasserr_re.match(str(err)): return + raise + self.story.append(Spacer(0*cm, 0.5*cm)) + self._showWidgetDemoCode(widget) + self.story.append(Spacer(0*cm, 0.5*cm)) + self._showWidgetDemo(widget) + self.story.append(Spacer(0*cm, 0.5*cm)) + self._showWidgetProperties(widget) + self.story.append(PageBreak()) + elif issubclass(aClass, Drawing): + drawing = aClass() + self.story.append(Spacer(0*cm, 0.5*cm)) + self._showDrawingCode(drawing) + self.story.append(Spacer(0*cm, 0.5*cm)) + self._showDrawingDemo(drawing) + self.story.append(Spacer(0*cm, 0.5*cm)) + + + def beginFunctions(self, names): + srch = string.join(names, ' ') + if string.find(string.join(names, ' '), ' sample') > -1: + PdfDocBuilder0.beginFunctions(self, names) + + + # Skip non-sample functions. + def beginFunction(self, name, doc, sig): + "Skip function for 'uninteresting' names." + + if name[:6] == 'sample': + PdfDocBuilder0.beginFunction(self, name, doc, sig) + + + def endFunction(self, name, doc, sig): + "Append a drawing to the story for special function names." + + if name[:6] != 'sample': + return + + if VERBOSE: + print 'GraphPdfDocBuilder.endFunction(%s...)' % name + PdfDocBuilder0.endFunction(self, name, doc, sig) + aFunc = eval('self.skeleton.moduleSpace.' + name) + drawing = aFunc() + + self.story.append(Spacer(0*cm, 0.5*cm)) + self._showFunctionDemoCode(aFunc) + self.story.append(Spacer(0*cm, 0.5*cm)) + self._showDrawingDemo(drawing) + + self.story.append(PageBreak()) + + + def _showFunctionDemoCode(self, function): + """Show a demo code of the function generating the drawing.""" + # Heading + self.story.append(Paragraph("Example", self.bt)) + self.story.append(Paragraph("", self.bt)) + + # Sample code + codeSample = inspect.getsource(function) + self.story.append(Preformatted(codeSample, self.code)) + + + def _showDrawingCode(self, drawing): + """Show code of the drawing class.""" + # Heading + #className = drawing.__class__.__name__ + self.story.append(Paragraph("Example", self.bt)) + + # Sample code + codeSample = inspect.getsource(drawing.__class__.__init__) + self.story.append(Preformatted(codeSample, self.code)) + + + def _showDrawingDemo(self, drawing): + """Show a graphical demo of the drawing.""" + + # Add the given drawing to the story. + # Ignored if no GD rendering available + # or the demo method does not return a drawing. + try: + flo = renderPDF.GraphicsFlowable(drawing) + self.story.append(Spacer(6,6)) + self.story.append(flo) + self.story.append(Spacer(6,6)) + except: + if VERBOSE: + print 'caught exception in _showDrawingDemo' + traceback.print_exc() + else: + pass + + + def _showWidgetDemo(self, widget): + """Show a graphical demo of the widget.""" + + # Get a demo drawing from the widget and add it to the story. + # Ignored if no GD rendering available + # or the demo method does not return a drawing. + try: + if VERIFY: + widget.verify() + drawing = widget.demo() + flo = renderPDF.GraphicsFlowable(drawing) + self.story.append(Spacer(6,6)) + self.story.append(flo) + self.story.append(Spacer(6,6)) + except: + if VERBOSE: + print 'caught exception in _showWidgetDemo' + traceback.print_exc() + else: + pass + + + def _showWidgetDemoCode(self, widget): + """Show a demo code of the widget.""" + # Heading + #className = widget.__class__.__name__ + self.story.append(Paragraph("Example", self.bt)) + + # Sample code + codeSample = inspect.getsource(widget.__class__.demo) + self.story.append(Preformatted(codeSample, self.code)) + + + def _showWidgetProperties(self, widget): + """Dump all properties of a widget.""" + + props = widget.getProperties() + keys = props.keys() + keys.sort() + lines = [] + for key in keys: + value = props[key] + + f = getStringIO() + pprint.pprint(value, f) + value = f.getvalue()[:-1] + valueLines = string.split(value, '\n') + for i in range(1, len(valueLines)): + valueLines[i] = ' '*(len(key)+3) + valueLines[i] + value = string.join(valueLines, '\n') + + lines.append('%s = %s' % (key, value)) + + text = join(lines, '\n') + self.story.append(Paragraph("Properties of Example Widget", self.bt)) + self.story.append(Paragraph("", self.bt)) + self.story.append(Preformatted(text, self.code)) + + +class GraphHtmlDocBuilder0(HtmlDocBuilder0): + "A class to write the skeleton of a Python source." + + fileSuffix = '.html' + + def beginModule(self, name, doc, imported): + # Defer displaying the module header info to later... + self.shouldDisplayModule = (name, doc, imported) + self.hasDisplayedModule = 0 + + + def endModule(self, name, doc, imported): + if self.hasDisplayedModule: + HtmlDocBuilder0.endModule(self, name, doc, imported) + + + def beginClasses(self, names): + # Defer displaying the module header info to later... + if self.shouldDisplayModule: + self.shouldDisplayClasses = names + + + # Skip all methods. + def beginMethod(self, name, doc, sig): + pass + + + def endMethod(self, name, doc, sig): + pass + + + def beginClass(self, name, doc, bases): + "Append a graphic demo of a widget at the end of a class." + + aClass = eval('self.skeleton.moduleSpace.' + name) + if issubclass(aClass, Widget): + if self.shouldDisplayModule: + modName, modDoc, imported = self.shouldDisplayModule + self.outLines.append('

%s

' % modName) + self.outLines.append('
%s
' % modDoc) + self.shouldDisplayModule = 0 + self.hasDisplayedModule = 1 + if self.shouldDisplayClasses: + self.outLines.append('

Classes

') + self.shouldDisplayClasses = 0 + + HtmlDocBuilder0.beginClass(self, name, doc, bases) + + + def endClass(self, name, doc, bases): + "Append a graphic demo of a widget at the end of a class." + + HtmlDocBuilder0.endClass(self, name, doc, bases) + + aClass = eval('self.skeleton.moduleSpace.' + name) + if issubclass(aClass, Widget): + widget = aClass() + self._showWidgetDemoCode(widget) + self._showWidgetDemo(widget) + self._showWidgetProperties(widget) + + + def beginFunctions(self, names): + if string.find(string.join(names, ' '), ' sample') > -1: + HtmlDocBuilder0.beginFunctions(self, names) + + + # Skip non-sample functions. + def beginFunction(self, name, doc, sig): + "Skip function for 'uninteresting' names." + + if name[:6] == 'sample': + HtmlDocBuilder0.beginFunction(self, name, doc, sig) + + + def endFunction(self, name, doc, sig): + "Append a drawing to the story for special function names." + + if name[:6] != 'sample': + return + + HtmlDocBuilder0.endFunction(self, name, doc, sig) + aFunc = eval('self.skeleton.moduleSpace.' + name) + drawing = aFunc() + + self._showFunctionDemoCode(aFunc) + self._showDrawingDemo(drawing, aFunc.__name__) + + + def _showFunctionDemoCode(self, function): + """Show a demo code of the function generating the drawing.""" + # Heading + self.outLines.append('

Example

') + + # Sample code + codeSample = inspect.getsource(function) + self.outLines.append('
%s
' % codeSample) + + + def _showDrawingDemo(self, drawing, name): + """Show a graphical demo of the drawing.""" + + # Add the given drawing to the story. + # Ignored if no GD rendering available + # or the demo method does not return a drawing. + try: + from reportlab.graphics import renderPM + modName = self.skeleton.getModuleName() + path = '%s-%s.jpg' % (modName, name) + renderPM.drawToFile(drawing, path, fmt='JPG') + self.outLines.append('

Demo

') + self.outLines.append(makeHtmlInlineImage(path)) + except: + if VERBOSE: + print 'caught exception in GraphHTMLDocBuilder._showDrawingDemo' + traceback.print_exc() + else: + pass + + + def _showWidgetDemo(self, widget): + """Show a graphical demo of the widget.""" + + # Get a demo drawing from the widget and add it to the story. + # Ignored if no GD rendering available + # or the demo method does not return a drawing. + try: + from reportlab.graphics import renderPM + drawing = widget.demo() + if VERIFY: + widget.verify() + modName = self.skeleton.getModuleName() + path = '%s-%s.jpg' % (modName, widget.__class__.__name__) + renderPM.drawToFile(drawing, path, fmt='JPG') + self.outLines.append('

Demo

') + self.outLines.append(makeHtmlInlineImage(path)) + except: + if VERBOSE: + + print 'caught exception in GraphHTMLDocBuilder._showWidgetDemo' + traceback.print_exc() + else: + pass + + + def _showWidgetDemoCode(self, widget): + """Show a demo code of the widget.""" + # Heading + #className = widget.__class__.__name__ + self.outLines.append('

Example Code

') + + # Sample code + codeSample = inspect.getsource(widget.__class__.demo) + self.outLines.append('
%s
' % codeSample) + self.outLines.append('') + + + def _showWidgetProperties(self, widget): + """Dump all properties of a widget.""" + + props = widget.getProperties() + keys = props.keys() + keys.sort() + lines = [] + for key in keys: + value = props[key] + + # Method 3 + f = getStringIO() + pprint.pprint(value, f) + value = f.getvalue()[:-1] + valueLines = string.split(value, '\n') + for i in range(1, len(valueLines)): + valueLines[i] = ' '*(len(key)+3) + valueLines[i] + value = string.join(valueLines, '\n') + + lines.append('%s = %s' % (key, value)) + text = join(lines, '\n') + self.outLines.append('

Properties of Example Widget

') + self.outLines.append('
%s
' % text) + self.outLines.append('') + + +# Highly experimental! +class PlatypusDocBuilder0(DocBuilder0): + "Document the skeleton of a Python module as a Platypus story." + + fileSuffix = '.pps' # A pickled Platypus story. + + def begin(self, name='', typ=''): + styleSheet = getSampleStyleSheet() + self.code = styleSheet['Code'] + self.bt = styleSheet['BodyText'] + self.story = [] + + + def end(self): + if self.packageName: + self.outPath = self.packageName + self.fileSuffix + elif self.skeleton: + self.outPath = self.skeleton.getModuleName() + self.fileSuffix + else: + self.outPath = '' + + if self.outPath: + f = open(self.outPath, 'w') + pickle.dump(self.story, f) + + + def beginPackage(self, name): + DocBuilder0.beginPackage(self, name) + self.story.append(Paragraph(name, self.bt)) + + + def beginModule(self, name, doc, imported): + story = self.story + bt = self.bt + + story.append(Paragraph(name, bt)) + story.append(XPreformatted(doc, bt)) + + + def beginClasses(self, names): + self.story.append(Paragraph('Classes', self.bt)) + + + def beginClass(self, name, doc, bases): + bt = self.bt + story = self.story + if bases: + bases = map(lambda b:b.__name__, bases) # hack + story.append(Paragraph('%s(%s)' % (name, join(bases, ', ')), bt)) + else: + story.append(Paragraph(name, bt)) + + story.append(XPreformatted(doc, bt)) + + + def beginMethod(self, name, doc, sig): + bt = self.bt + story = self.story + story.append(Paragraph(name+sig, bt)) + story.append(XPreformatted(doc, bt)) + + + def beginFunctions(self, names): + if names: + self.story.append(Paragraph('Functions', self.bt)) + + + def beginFunction(self, name, doc, sig): + bt = self.bt + story = self.story + story.append(Paragraph(name+sig, bt)) + story.append(XPreformatted(doc, bt)) + + +#################################################################### +# +# Main +# +#################################################################### + +def printUsage(): + """graphdocpy.py - Automated documentation for the RL Graphics library. + +Usage: python graphdocpy.py [options] + + [options] + -h Print this help message. + + -f name Use the document builder indicated by 'name', + e.g. Html, Pdf. + + -m module Generate document for module named 'module'. + 'module' may follow any of these forms: + - docpy.py + - docpy + - c:\\test\\docpy + and can be any of these: + - standard Python modules + - modules in the Python search path + - modules in the current directory + + -p package Generate document for package named 'package' + (default is 'reportlab.graphics'). + 'package' may follow any of these forms: + - reportlab + - reportlab.graphics.charts + - c:\\test\\reportlab + and can be any of these: + - standard Python packages (?) + - packages in the Python search path + - packages in the current directory + + -s Silent mode (default is unset). + +Examples: + + python graphdocpy.py reportlab.graphics + python graphdocpy.py -m signsandsymbols.py -f Pdf + python graphdocpy.py -m flags.py -f Html + python graphdocpy.py -m barchart1.py +""" + + +# The following functions, including main(), are actually +# the same as in docpy.py (except for some defaults). + +def documentModule0(pathOrName, builder, opts={}): + """Generate documentation for one Python file in some format. + + This handles Python standard modules like string, custom modules + on the Python search path like e.g. docpy as well as modules + specified with their full path like C:/tmp/junk.py. + + The doc file will always be saved in the current directory with + a basename equal to that of the module, e.g. docpy. + """ + cwd = os.getcwd() + + # Append directory to Python search path if we get one. + dirName = os.path.dirname(pathOrName) + if dirName: + sys.path.append(dirName) + + # Remove .py extension from module name. + if pathOrName[-3:] == '.py': + modname = pathOrName[:-3] + else: + modname = pathOrName + + # Remove directory paths from module name. + if dirName: + modname = os.path.basename(modname) + + # Load the module. + try: + module = __import__(modname) + except: + print 'Failed to import %s.' % modname + os.chdir(cwd) + return + + # Do the real documentation work. + s = ModuleSkeleton0() + s.inspect(module) + builder.write(s) + + # Remove appended directory from Python search path if we got one. + if dirName: + del sys.path[-1] + + os.chdir(cwd) + + +def _packageWalkCallback((builder, opts), dirPath, files): + "A callback function used when waking over a package tree." + #must CD into a directory to document the module correctly + cwd = os.getcwd() + os.chdir(dirPath) + + + # Skip __init__ files. + files = filter(lambda f:f != '__init__.py', files) + + files = filter(lambda f:f[-3:] == '.py', files) + for f in files: + path = os.path.join(dirPath, f) +## if not opts.get('isSilent', 0): +## print path + builder.indentLevel = builder.indentLevel + 1 + #documentModule0(path, builder) + documentModule0(f, builder) + builder.indentLevel = builder.indentLevel - 1 + #CD back out + os.chdir(cwd) + +def documentPackage0(pathOrName, builder, opts={}): + """Generate documentation for one Python package in some format. + + 'pathOrName' can be either a filesystem path leading to a Python + package or package name whose path will be resolved by importing + the top-level module. + + The doc file will always be saved in the current directory with + a basename equal to that of the package, e.g. reportlab.lib. + """ + + # Did we get a package path with OS-dependant seperators...? + if os.sep in pathOrName: + path = pathOrName + name = os.path.splitext(os.path.basename(path))[0] + # ... or rather a package name? + else: + name = pathOrName + package = __import__(name) + # Some special care needed for dotted names. + if '.' in name: + subname = 'package' + name[find(name, '.'):] + package = eval(subname) + path = os.path.dirname(package.__file__) + + cwd = os.getcwd() + os.chdir(path) + builder.beginPackage(name) + os.path.walk(path, _packageWalkCallback, (builder, opts)) + builder.endPackage(name) + os.chdir(cwd) + + +def makeGraphicsReference(outfilename): + "Make graphics_reference.pdf" + builder = GraphPdfDocBuilder0() + + builder.begin(name='reportlab.graphics', typ='package') + documentPackage0('reportlab.graphics', builder, {'isSilent': 0}) + builder.end(outfilename) + print 'made graphics reference in %s' % outfilename + +def main(): + "Handle command-line options and trigger corresponding action." + + opts, args = getopt.getopt(sys.argv[1:], 'hsf:m:p:') + + # Make an options dictionary that is easier to use. + optsDict = {} + for k, v in opts: + optsDict[k] = v + hasOpt = optsDict.has_key + + # On -h print usage and exit immediately. + if hasOpt('-h'): + print printUsage.__doc__ + sys.exit(0) + + # On -s set silent mode. + isSilent = hasOpt('-s') + + # On -f set the appropriate DocBuilder to use or a default one. + builder = { 'Pdf': GraphPdfDocBuilder0, + 'Html': GraphHtmlDocBuilder0, + }[optsDict.get('-f', 'Pdf')]() + + # Set default module or package to document. + if not hasOpt('-p') and not hasOpt('-m'): + optsDict['-p'] = 'reportlab.graphics' + + # Save a few options for further use. + options = {'isSilent':isSilent} + + # Now call the real documentation functions. + if hasOpt('-m'): + nameOrPath = optsDict['-m'] + if not isSilent: + print "Generating documentation for module %s..." % nameOrPath + builder.begin(name=nameOrPath, typ='module') + documentModule0(nameOrPath, builder, options) + elif hasOpt('-p'): + nameOrPath = optsDict['-p'] + if not isSilent: + print "Generating documentation for package %s..." % nameOrPath + builder.begin(name=nameOrPath, typ='package') + documentPackage0(nameOrPath, builder, options) + builder.end() + + if not isSilent: + print "Saved %s." % builder.outPath + + #if doing the usual, put a copy in docs + if builder.outPath == 'reportlab.graphics.pdf': + import shutil, reportlab + dst = os.path.join(os.path.dirname(reportlab.__file__),'docs','graphics_reference.pdf') + shutil.copyfile('reportlab.graphics.pdf', dst) + if not isSilent: + print 'copied to '+dst + +def makeSuite(): + "standard test harness support - run self as separate process" + from reportlab.test.utils import ScriptThatMakesFileTest + return ScriptThatMakesFileTest('tools/docco', + 'graphdocpy.py', + 'reportlab.graphics.pdf') + +if __name__ == '__main__': + main() diff --git a/bin/reportlab/tools/docco/rl_doc_utils.py b/bin/reportlab/tools/docco/rl_doc_utils.py new file mode 100644 index 00000000000..a105b9d7e88 --- /dev/null +++ b/bin/reportlab/tools/docco/rl_doc_utils.py @@ -0,0 +1,411 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/rl_doc_utils.py +__version__=''' $Id: rl_doc_utils.py 2830 2006-04-05 15:18:32Z rgbecker $ ''' + + +__doc__ = """ +This module contains utilities for generating guides +""" + +import os, sys, glob +import string + +from rltemplate import RLDocTemplate +from stylesheet import getStyleSheet +styleSheet = getStyleSheet() + +#from reportlab.platypus.doctemplate import SimpleDocTemplate +from reportlab.lib.units import inch +from reportlab.lib.pagesizes import letter, A4, A5, A3 # latter two for testing +from reportlab.rl_config import defaultPageSize +from reportlab.platypus import figures +from reportlab.platypus import Paragraph, Spacer, Preformatted,\ + PageBreak, CondPageBreak, Flowable, Table, TableStyle, \ + NextPageTemplate, KeepTogether, Image, XPreformatted +from reportlab.lib.styles import ParagraphStyle +from reportlab.lib import colors +from reportlab.lib.sequencer import getSequencer + +import examples + +appmode=0 + + +from t_parse import Template +QFcodetemplate = Template("X$X$", "X") +QFreptemplate = Template("X^X^", "X") +codesubst = "%s%s" +QFsubst = "%s%s" + + +def quickfix(text): + """inside text find any subsequence of form $subsequence$. + Format the subsequence as code. If similarly if text contains ^arg^ + format the arg as replaceable. The escape sequence for literal + $ is $\\$ (^ is ^\\^. + """ + from string import join + for (template,subst) in [(QFcodetemplate, codesubst), (QFreptemplate, QFsubst)]: + fragment = text + parts = [] + try: + while fragment: + try: + (matches, index) = template.PARSE(fragment) + except: raise ValueError + else: + [prefix, code] = matches + if code == "\\": + part = fragment[:index] + else: + part = subst % (prefix, code) + parts.append(part) + fragment = fragment[index:] + except ValueError: + parts.append(fragment) + text = join(parts, "") + return text +#print quickfix("$testing$ testing $one$ ^two^ $three(^four^)$") + + + +H1 = styleSheet['Heading1'] +H2 = styleSheet['Heading2'] +H3 = styleSheet['Heading3'] +H4 = styleSheet['Heading4'] +B = styleSheet['BodyText'] +BU = styleSheet['Bullet'] +Comment = styleSheet['Comment'] +Centred = styleSheet['Centred'] +Caption = styleSheet['Caption'] + +#set up numbering +seq = getSequencer() +seq.setFormat('Chapter','1') +seq.setFormat('Section','1') +seq.setFormat('Appendix','A') +seq.setFormat('Figure', '1') +seq.chain('Chapter','Section') +seq.chain('Chapter','Figure') + +lessonnamestyle = H2 +discussiontextstyle = B +exampletextstyle = styleSheet['Code'] +# size for every example +examplefunctionxinches = 5.5 +examplefunctionyinches = 3 +examplefunctiondisplaysizes = (examplefunctionxinches*inch, examplefunctionyinches*inch) + +def getJustFontPaths(): + '''return afm and pfb for Just's files''' + import reportlab + folder = os.path.dirname(reportlab.__file__) + os.sep + 'fonts' + return os.path.join(folder, 'LeERC___.AFM'), os.path.join(folder, 'LeERC___.PFB') + +# for testing +def NOP(*x,**y): + return None + +def CPage(inches): + getStory().append(CondPageBreak(inches*inch)) + +def newPage(): + getStory().append(PageBreak()) + +def nextTemplate(templName): + f = NextPageTemplate(templName) + getStory().append(f) + +def disc(text, klass=Paragraph, style=discussiontextstyle): + text = quickfix(text) + P = klass(text, style) + getStory().append(P) + +def restartList(): + getSequencer().reset('list1') + +def list(text, doBullet=1): + text=quickfix(text) + if doBullet: + text='.'+text + P = Paragraph(text, BU) + getStory().append(P) + +def bullet(text): + text='\xe2\x80\xa2' + quickfix(text) + P = Paragraph(text, BU) + getStory().append(P) + +def eg(text,before=0.1,after=0): + space(before) + disc(text, klass=Preformatted, style=exampletextstyle) + space(after) + +def space(inches=1./6): + if inches: getStory().append(Spacer(0,inches*inch)) + +def EmbeddedCode(code,name='t'): + eg(code) + disc("produces") + exec code+("\ngetStory().append(%s)\n"%name) + +def startKeep(): + return len(getStory()) + +def endKeep(s): + S = getStory() + k = KeepTogether(S[s:]) + S[s:] = [k] + +def title(text): + """Use this for the document title only""" + disc(text,style=styleSheet['Title']) + +#AR 3/7/2000 - defining three new levels of headings; code +#should be swapped over to using them. + +def heading1(text): + """Use this for chapters. Lessons within a big chapter + should now use heading2 instead. Chapters get numbered.""" + getStory().append(PageBreak()) + p = Paragraph('Chapter ' + quickfix(text), H1) + getStory().append(p) + +def Appendix1(text,): + global appmode + getStory().append(PageBreak()) + if not appmode: + seq.setFormat('Chapter','A') + seq.reset('Chapter') + appmode = 1 + p = Paragraph('Appendix ' + quickfix(text), H1) + getStory().append(p) + +def heading2(text): + """Used to be 'lesson'""" + getStory().append(CondPageBreak(inch)) + p = Paragraph('' + quickfix(text), H2) + getStory().append(p) + +def heading3(text): + """Used to be most of the plain old 'head' sections""" + getStory().append(CondPageBreak(inch)) + p = Paragraph(quickfix(text), H3) + getStory().append(p) + +def image(path, width=None, height=None ): + s = startKeep() + space(.2) + import reportlab + rlDocImageDir = os.path.join(os.path.dirname(reportlab.__file__), 'docs','images') + getStory().append(Image(os.path.join(rlDocImageDir,path),width,height)) + space(.2) + endKeep(s) + +def heading4(text): + """Used to be most of the plain old 'head' sections""" + getStory().append(CondPageBreak(inch)) + p = Paragraph(quickfix(text), H4) + getStory().append(p) + +def todo(text): + """Used for notes to ourselves""" + getStory().append(Paragraph(quickfix(text), Comment)) + +def centred(text): + getStory().append(Paragraph(quickfix(text), Centred)) + +def caption(text): + getStory().append(Paragraph(quickfix(text), Caption)) + +class Illustration(figures.Figure): + """The examples are all presented as functions which do + something to a canvas, with a constant height and width + used. This puts them inside a figure box with a caption.""" + + def __init__(self, operation, caption, width=None, height=None): + stdwidth, stdheight = examplefunctiondisplaysizes + if not width: + width = stdwidth + if not height: + height = stdheight + #figures.Figure.__init__(self, stdwidth * 0.75, stdheight * 0.75) + figures.Figure.__init__(self, width, height, + 'Figure : ' + quickfix(caption)) + self.operation = operation + + def drawFigure(self): + #shrink it a little... + #self.canv.scale(0.75, 0.75) + self.operation(self.canv) + + +def illust(operation, caption, width=None, height=None): + i = Illustration(operation, caption, width=width, height=height) + getStory().append(i) + + +class GraphicsDrawing(Illustration): + """Lets you include reportlab/graphics drawings seamlessly, + with the right numbering.""" + def __init__(self, drawing, caption): + figures.Figure.__init__(self, + drawing.width, + drawing.height, + 'Figure : ' + quickfix(caption) + ) + self.drawing = drawing + + def drawFigure(self): + d = self.drawing + d.wrap(d.width, d.height) + d.drawOn(self.canv, 0, 0) + +def draw(drawing, caption): + d = GraphicsDrawing(drawing, caption) + getStory().append(d) + +class ParaBox(figures.Figure): + """Illustrates paragraph examples, with style attributes on the left""" + descrStyle = ParagraphStyle('description', + fontName='Courier', + fontSize=8, + leading=9.6) + + def __init__(self, text, style, caption): + figures.Figure.__init__(self, 0, 0, caption) + self.text = text + self.style = style + self.para = Paragraph(text, style) + + styleText = self.getStyleText(style) + self.pre = Preformatted(styleText, self.descrStyle) + + def wrap(self, availWidth, availHeight): + """Left 30% is for attributes, right 50% for sample, + 10% gutter each side.""" + self.x0 = availWidth * 0.05 #left of box + self.x1 = availWidth * 0.1 #left of descriptive text + self.x2 = availWidth * 0.5 #left of para itself + self.x3 = availWidth * 0.9 #right of para itself + self.x4 = availWidth * 0.95 #right of box + self.width = self.x4 - self.x0 + self.dx = 0.5 * (availWidth - self.width) + + paw, self.pah = self.para.wrap(self.x3 - self.x2, availHeight) + self.pah = self.pah + self.style.spaceBefore + self.style.spaceAfter + prw, self.prh = self.pre.wrap(self.x2 - self.x1, availHeight) + self.figureHeight = max(self.prh, self.pah) * 10.0/9.0 + return figures.Figure.wrap(self, availWidth, availHeight) + + def getStyleText(self, style): + """Converts style to preformatted block of text""" + lines = [] + for (key, value) in style.__dict__.items(): + lines.append('%s = %s' % (key, value)) + lines.sort() + return string.join(lines, '\n') + + def drawFigure(self): + + #now we fill in the bounding box and before/after boxes + self.canv.saveState() + self.canv.setFillGray(0.95) + self.canv.setDash(1,3) + self.canv.rect(self.x2 - self.x0, + self.figureHeight * 0.95 - self.pah, + self.x3-self.x2, self.para.height, + fill=1,stroke=1) + + self.canv.setFillGray(0.90) + self.canv.rect(self.x2 - self.x0, #spaceBefore + self.figureHeight * 0.95 - self.pah + self.para.height, + self.x3-self.x2, self.style.spaceBefore, + fill=1,stroke=1) + + self.canv.rect(self.x2 - self.x0, #spaceBefore + self.figureHeight * 0.95 - self.pah - self.style.spaceAfter, + self.x3-self.x2, self.style.spaceAfter, + fill=1,stroke=1) + + self.canv.restoreState() + #self.canv.setFillColor(colors.yellow) + self.para.drawOn(self.canv, self.x2 - self.x0, + self.figureHeight * 0.95 - self.pah) + self.pre.drawOn(self.canv, self.x1 - self.x0, + self.figureHeight * 0.95 - self.prh) + + + def getStyleText(self, style): + """Converts style to preformatted block of text""" + lines = [] + for (key, value) in style.__dict__.items(): + if key not in ('name','parent'): + lines.append('%s = %s' % (key, value)) + return string.join(lines, '\n') + + +class ParaBox2(figures.Figure): + """Illustrates a paragraph side-by-side with the raw + text, to show how the XML works.""" + def __init__(self, text, caption): + figures.Figure.__init__(self, 0, 0, caption) + descrStyle = ParagraphStyle('description', + fontName='Courier', + fontSize=8, + leading=9.6) + textStyle = B + self.text = text + self.left = Paragraph('', descrStyle) + self.right = Paragraph(text, B) + + + def wrap(self, availWidth, availHeight): + self.width = availWidth * 0.9 + colWidth = 0.4 * self.width + lw, self.lh = self.left.wrap(colWidth, availHeight) + rw, self.rh = self.right.wrap(colWidth, availHeight) + self.figureHeight = max(self.lh, self.rh) * 10.0/9.0 + return figures.Figure.wrap(self, availWidth, availHeight) + + def drawFigure(self): + self.left.drawOn(self.canv, + self.width * 0.05, + self.figureHeight * 0.95 - self.lh + ) + self.right.drawOn(self.canv, + self.width * 0.55, + self.figureHeight * 0.95 - self.rh + ) + +def parabox(text, style, caption): + p = ParaBox(text, style, + 'Figure : ' + quickfix(caption) + ) + getStory().append(p) + +def parabox2(text, caption): + p = ParaBox2(text, + 'Figure : ' + quickfix(caption) + ) + getStory().append(p) + +def pencilnote(): + getStory().append(examples.NoteAnnotation()) + + +from reportlab.lib.colors import tan, green +def handnote(xoffset=0, size=None, fillcolor=tan, strokecolor=green): + getStory().append(examples.HandAnnotation(xoffset,size,fillcolor,strokecolor)) + + +#make a singleton, created when requested rather +#than each time a chapter imports it. +_story = [] +def setStory(story=[]): + global _story + _story = story +def getStory(): + return _story diff --git a/bin/reportlab/tools/docco/rltemplate.py b/bin/reportlab/tools/docco/rltemplate.py new file mode 100644 index 00000000000..466660e639f --- /dev/null +++ b/bin/reportlab/tools/docco/rltemplate.py @@ -0,0 +1,135 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/rltemplate.py +# doc template for RL manuals. Currently YAML is hard-coded +#to use this, which is wrong. + + +from reportlab.platypus import PageTemplate, \ + BaseDocTemplate, Frame, Paragraph +from reportlab.lib.units import inch, cm +from reportlab.rl_config import defaultPageSize + + +class FrontCoverTemplate(PageTemplate): + def __init__(self, id, pageSize=defaultPageSize): + self.pageWidth = pageSize[0] + self.pageHeight = pageSize[1] + frame1 = Frame(inch, + 3*inch, + self.pageWidth - 2*inch, + self.pageHeight - 518, id='cover') + PageTemplate.__init__(self, id, [frame1]) # note lack of onPage + + def afterDrawPage(self, canvas, doc): + canvas.saveState() + canvas.drawImage('../images/replogo.gif',2*inch, 8*inch) + + + canvas.setFont('Times-Roman', 10) + canvas.line(inch, 120, self.pageWidth - inch, 120) + + canvas.drawString(inch, 100, '165 The Broadway') + canvas.drawString(inch, 88, 'Wimbledon') + canvas.drawString(inch, 76, 'London SW19 1NE') + canvas.drawString(inch, 64, 'United Kingdom') + + canvas.restoreState() + + +class OneColumnTemplate(PageTemplate): + def __init__(self, id, pageSize=defaultPageSize): + self.pageWidth = pageSize[0] + self.pageHeight = pageSize[1] + frame1 = Frame(inch, + inch, + self.pageWidth - 2*inch, + self.pageHeight - 2*inch, + id='normal') + PageTemplate.__init__(self, id, [frame1]) # note lack of onPage + + def afterDrawPage(self, canvas, doc): + y = self.pageHeight - 50 + canvas.saveState() + canvas.setFont('Times-Roman', 10) + canvas.drawString(inch, y+8, doc.title) + canvas.drawRightString(self.pageWidth - inch, y+8, doc.chapter) + canvas.line(inch, y, self.pageWidth - inch, y) + canvas.drawCentredString(doc.pagesize[0] / 2, 0.75*inch, 'Page %d' % canvas.getPageNumber()) + canvas.restoreState() + +class TwoColumnTemplate(PageTemplate): + def __init__(self, id, pageSize=defaultPageSize): + self.pageWidth = pageSize[0] + self.pageHeight = pageSize[1] + colWidth = 0.5 * (self.pageWidth - 2.25*inch) + frame1 = Frame(inch, + inch, + colWidth, + self.pageHeight - 2*inch, + id='leftCol') + frame2 = Frame(0.5 * self.pageWidth + 0.125, + inch, + colWidth, + self.pageHeight - 2*inch, + id='rightCol') + PageTemplate.__init__(self, id, [frame1, frame2]) # note lack of onPage + + def afterDrawPage(self, canvas, doc): + y = self.pageHeight - 50 + canvas.saveState() + canvas.setFont('Times-Roman', 10) + canvas.drawString(inch, y+8, doc.title) + canvas.drawRightString(self.pageWidth - inch, y+8, doc.chapter) + canvas.line(inch, y, self.pageWidth - inch, y*inch) + canvas.drawCentredString(doc.pagesize[0] / 2, 0.75*inch, 'Page %d' % canvas.getPageNumber()) + canvas.restoreState() + + +class RLDocTemplate(BaseDocTemplate): + def afterInit(self): + self.addPageTemplates(FrontCoverTemplate('Cover', self.pagesize)) + self.addPageTemplates(OneColumnTemplate('Normal', self.pagesize)) + self.addPageTemplates(TwoColumnTemplate('TwoColumn', self.pagesize)) + + #just playing + self.title = "(Document Title Goes Here)" + self.chapter = "(No chapter yet)" + self.chapterNo = 1 #unique keys + self.sectionNo = 1 # unique keys + +## # AR hack +## self.counter = 1 + def beforeDocument(self): + self.canv.showOutline() + + def afterFlowable(self, flowable): + """Detect Level 1 and 2 headings, build outline, + and track chapter title.""" + if isinstance(flowable, Paragraph): + style = flowable.style.name + +## #AR debug text +## try: +## print '%d: %s...' % (self.counter, flowable.getPlainText()[0:40]) +## except AttributeError: +## print '%d: (something with ABag)' % self.counter +## self.counter = self.counter + 1 + + if style == 'Title': + self.title = flowable.getPlainText() + elif style == 'Heading1': + self.chapter = flowable.getPlainText() + key = 'ch%d' % self.chapterNo + self.canv.bookmarkPage(key) + self.canv.addOutlineEntry(flowable.getPlainText(), + key, 0, 0) + self.chapterNo = self.chapterNo + 1 + self.sectionNo = 1 + elif style == 'Heading2': + self.section = flowable.text + key = 'ch%ds%d' % (self.chapterNo, self.sectionNo) + self.canv.bookmarkPage(key) + self.canv.addOutlineEntry(flowable.getPlainText(), + key, 1, 0) + self.sectionNo = self.sectionNo + 1 \ No newline at end of file diff --git a/bin/reportlab/tools/docco/stylesheet.py b/bin/reportlab/tools/docco/stylesheet.py new file mode 100644 index 00000000000..9e8d2c65840 --- /dev/null +++ b/bin/reportlab/tools/docco/stylesheet.py @@ -0,0 +1,165 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/stylesheet.py +#standard stylesheet for our manuals +from reportlab.lib.styles import StyleSheet1, ParagraphStyle +from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY +from reportlab.lib import colors + + +def getStyleSheet(): + """Returns a stylesheet object""" + stylesheet = StyleSheet1() + + stylesheet.add(ParagraphStyle(name='Normal', + fontName='Times-Roman', + fontSize=10, + leading=12, + spaceBefore=6) + ) + + stylesheet.add(ParagraphStyle(name='Comment', + fontName='Times-Italic') + ) + + stylesheet.add(ParagraphStyle(name='Indent0', + leftIndent=18,) + ) + + stylesheet.add(ParagraphStyle(name='Indent1', + leftIndent=36, + firstLineIndent=0, + spaceBefore=1, + spaceAfter=7) + ) + + stylesheet.add(ParagraphStyle(name='Indent2', + leftIndent=50, + firstLineIndent=0, + spaceAfter=100) + ) + + stylesheet.add(ParagraphStyle(name='BodyText', + parent=stylesheet['Normal'], + spaceBefore=6) + ) + stylesheet.add(ParagraphStyle(name='Italic', + parent=stylesheet['BodyText'], + fontName = 'Times-Italic') + ) + + stylesheet.add(ParagraphStyle(name='Heading1', + parent=stylesheet['Normal'], + fontName = 'Times-Bold', + alignment=TA_CENTER, + fontSize=18, + leading=22, + spaceAfter=6), + alias='h1') + + stylesheet.add(ParagraphStyle(name='Heading2', + parent=stylesheet['Normal'], + fontName = 'Times-Bold', + fontSize=14, + leading=17, + spaceBefore=12, + spaceAfter=6), + alias='h2') + + stylesheet.add(ParagraphStyle(name='Heading3', + parent=stylesheet['Normal'], + fontName = 'Times-BoldItalic', + fontSize=12, + leading=14, + spaceBefore=12, + spaceAfter=6), + alias='h3') + + stylesheet.add(ParagraphStyle(name='Heading4', + parent=stylesheet['Normal'], + fontName = 'Times-BoldItalic', + spaceBefore=10, + spaceAfter=4), + alias='h4') + + stylesheet.add(ParagraphStyle(name='Title', + parent=stylesheet['Normal'], + fontName = 'Times-Bold', + fontSize=32, + leading=40, + spaceAfter=36, + alignment=TA_CENTER + ), + alias='t') + + stylesheet.add(ParagraphStyle(name='Bullet', + parent=stylesheet['Normal'], + firstLineIndent=0, + leftIndent=54, + bulletIndent=18, + spaceBefore=0, + bulletFontName='Symbol'), + alias='bu') + + stylesheet.add(ParagraphStyle(name='Definition', + parent=stylesheet['Normal'], + firstLineIndent=0, + leftIndent=36, + bulletIndent=0, + spaceBefore=6, + bulletFontName='Times-BoldItalic'), + alias='df') + + stylesheet.add(ParagraphStyle(name='Code', + parent=stylesheet['Normal'], + fontName='Courier', + textColor=colors.navy, + fontSize=8, + leading=8.8, + leftIndent=36, + firstLineIndent=0)) + + stylesheet.add(ParagraphStyle(name='Link', + parent=stylesheet['Code'], + spaceAfter=7, + spaceBefore=0, + leftIndent=55)) + + stylesheet.add(ParagraphStyle(name='FunctionHeader', + parent=stylesheet['Normal'], + fontName='Courier-Bold', + fontSize=8, + leading=8.8)) + + stylesheet.add(ParagraphStyle(name='DocString', + parent=stylesheet['Normal'], + fontName='Courier', + fontSize=8, + leftIndent=18, + leading=8.8)) + + stylesheet.add(ParagraphStyle(name='DocStringIndent', + parent=stylesheet['Normal'], + fontName='Courier', + fontSize=8, + leftIndent=36, + leading=8.8)) + + stylesheet.add(ParagraphStyle(name='URL', + parent=stylesheet['Normal'], + fontName='Courier', + textColor=colors.navy, + alignment=TA_CENTER), + alias='u') + + stylesheet.add(ParagraphStyle(name='Centred', + parent=stylesheet['Normal'], + alignment=TA_CENTER + )) + + stylesheet.add(ParagraphStyle(name='Caption', + parent=stylesheet['Centred'], + fontName='Times-Italic' + )) + + return stylesheet \ No newline at end of file diff --git a/bin/reportlab/tools/docco/t_parse.py b/bin/reportlab/tools/docco/t_parse.py new file mode 100644 index 00000000000..0dc66c3c879 --- /dev/null +++ b/bin/reportlab/tools/docco/t_parse.py @@ -0,0 +1,247 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/t_parse.py +""" +Template parsing module inspired by REXX (with thanks to Donn Cave for discussion). + +Template initialization has the form: + T = Template(template_string, wild_card_marker, single_char_marker, + x = regex_x, y = regex_y, ...) +Parsing has the form + ([match1, match2, ..., matchn], lastindex) = T.PARSE(string) + +Only the first argument is mandatory. + +The resultant object efficiently parses strings that match the template_string, +giving a list of substrings that correspond to each "directive" of the template. + +Template directives: + + Wildcard: + The template may be initialized with a wildcard that matches any string + up to the string matching the next directive (which may not be a wild + card or single character marker) or the next literal sequence of characters + of the template. The character that represents a wildcard is specified + by the wild_card_marker parameter, which has no default. + + For example, using X as the wildcard: + + + >>> T = Template("prefixXinteriorX", "X") + >>> T.PARSE("prefix this is before interior and this is after") + ([' this is before ', ' and this is after'], 47) + >>> T = Template("X", "X") + >>> T.PARSE('go to index') + (['A HREF="index.html"', 'go to index', '/A'], 36) + + Obviously the character used to represent the wildcard must be distinct + from the characters used to represent literals or other directives. + + Fixed length character sequences: + The template may have a marker character which indicates a fixed + length field. All adjacent instances of this marker will be matched + by a substring of the same length in the parsed string. For example: + + >>> T = Template("NNN-NN-NNNN", single_char_marker="N") + >>> T.PARSE("1-2-34-5-12") + (['1-2', '34', '5-12'], 11) + >>> T.PARSE("111-22-3333") + (['111', '22', '3333'], 11) + >>> T.PARSE("1111-22-3333") + ValueError: literal not found at (3, '-') + + A template may have multiple fixed length markers, which allows fixed + length fields to be adjacent, but recognized separately. For example: + + >>> T = Template("MMDDYYX", "X", "MDY") + >>> T.PARSE("112489 Somebody's birthday!") + (['11', '24', '89', " Somebody's birthday!"], 27) + + Regular expression markers: + The template may have markers associated with regular expressions. + the regular expressions may be either string represenations of compiled. + For example: + >>> T = Template("v: s i", v=id, s=str, i=int) + >>> T.PARSE("this_is_an_identifier: 'a string' 12344") + (['this_is_an_identifier', "'a string'", '12344'], 39) + >>> + Here id, str, and int are regular expression conveniences provided by + this module. + + Directive markers may be mixed and matched, except that wildcards cannot precede + wildcards or single character markers. + Example: +>>> T = Template("ssnum: NNN-NN-NNNN, fn=X, ln=X, age=I, quote=Q", "X", "N", I=int, Q=str) +>>> T.PARSE("ssnum: 123-45-6789, fn=Aaron, ln=Watters, age=13, quote='do be do be do'") +(['123', '45', '6789', 'Aaron', 'Watters', '13', "'do be do be do'"], 72) +>>> + +""" + +import re, string +from types import StringType +from string import find + +# +# template parsing +# +# EG: T = Template("(NNN)NNN-NNNN X X", "X", "N") +# ([area, exch, ext, fn, ln], index) = T.PARSE("(908)949-2726 Aaron Watters") +# +class Template: + + def __init__(self, + template, + wild_card_marker=None, + single_char_marker=None, + **marker_to_regex_dict): + self.template = template + self.wild_card = wild_card_marker + self.char = single_char_marker + # determine the set of markers for this template + markers = marker_to_regex_dict.keys() + if wild_card_marker: + markers.append(wild_card_marker) + if single_char_marker: + for ch in single_char_marker: # allow multiple scm's + markers.append(ch) + self.char = single_char_primary = single_char_marker[0] + self.markers = markers + for mark in markers: + if len(mark)>1: + raise ValueError, "Marks must be single characters: "+`mark` + # compile the regular expressions if needed + self.marker_dict = marker_dict = {} + for (mark, rgex) in marker_to_regex_dict.items(): + if type(rgex) == StringType: + rgex = re.compile(rgex) + marker_dict[mark] = rgex + # determine the parse sequence + parse_seq = [] + # dummy last char + lastchar = None + index = 0 + last = len(template) + # count the number of directives encountered + ndirectives = 0 + while index s blah", s=str) + s = "' <-- a string --> ' --> 'blah blah another string blah' blah" + print T1.PARSE(s) + + T2 = Template("s --> NNNiX", "X", "N", s=str, i=int) + print T2.PARSE("'A STRING' --> 15964653alpha beta gamma") + + T3 = Template("XsXi", "X", "N", s=str, i=int) + print T3.PARSE("prefix'string'interior1234junk not parsed") + + T4 = Template("MMDDYYX", "X", "MDY") + print T4.PARSE("122961 Somebody's birthday!") + + +if __name__=="__main__": test() \ No newline at end of file diff --git a/bin/reportlab/tools/docco/yaml.py b/bin/reportlab/tools/docco/yaml.py new file mode 100644 index 00000000000..af31788622e --- /dev/null +++ b/bin/reportlab/tools/docco/yaml.py @@ -0,0 +1,201 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/yaml.py +# parses "Yet Another Markup Language" into a list of tuples. +# Each tuple says what the data is e.g. +# ('Paragraph', 'Heading1', 'Why Reportlab Rules') +# and the pattern depends on type. +""" +Parser for "Aaron's Markup Language" - a markup language +which is easier to type in than XML, yet gives us a +reasonable selection of formats. + +The general rule is that if a line begins with a '.', +it requires special processing. Otherwise lines +are concatenated to paragraphs, and blank lines +separate paragraphs. + +If the line ".foo bar bletch" is encountered, +it immediately ends and writes out any current +paragraph. + +It then looks for a parser method called 'foo'; +if found, it is called with arguments (bar, bletch). + +If this is not found, it assumes that 'foo' is a +paragraph style, and the text for the first line +of the paragraph is 'bar bletch'. It would be +up to the formatter to decide whether on not 'foo' +was a valid paragraph. + +Special commands understood at present are: +.image filename +- adds the image to the document +.beginPre Code +- begins a Preformatted object in style 'Code' +.endPre +- ends a preformatted object. +""" + + +import sys +import string +import imp +import codegrab + +#modes: +PLAIN = 1 +PREFORMATTED = 2 + +BULLETCHAR = '\267' # assumes font Symbol, but works on all platforms + +class Parser: + def __init__(self): + self.reset() + + def reset(self): + self._lineNo = 0 + self._style = 'Normal' # the default + self._results = [] + self._buf = [] + self._mode = PLAIN + + def parseFile(self, filename): + #returns list of objects + data = open(filename, 'r').readlines() + + for line in data: + #strip trailing newlines + self.readLine(line[:-1]) + self.endPara() + return self._results + + def readLine(self, line): + #this is the inner loop + self._lineNo = self._lineNo + 1 + stripped = string.lstrip(line) + if len(stripped) == 0: + if self._mode == PLAIN: + self.endPara() + else: #preformatted, append it + self._buf.append(line) + elif line[0]=='.': + # we have a command of some kind + self.endPara() + words = string.split(stripped[1:]) + cmd, args = words[0], words[1:] + + #is it a parser method? + if hasattr(self.__class__, cmd): + method = eval('self.'+cmd) + #this was very bad; any type error in the method was hidden + #we have to hack the traceback + try: + apply(method, tuple(args)) + except TypeError, err: + sys.stderr.write("Parser method: apply(%s,%s) %s at line %d\n" % (cmd, tuple(args), err, self._lineNo)) + raise + else: + # assume it is a paragraph style - + # becomes the formatter's problem + self.endPara() #end the last one + words = string.split(stripped, ' ', 1) + assert len(words)==2, "Style %s but no data at line %d" % (words[0], self._lineNo) + (styletag, data) = words + self._style = styletag[1:] + self._buf.append(data) + else: + #we have data, add to para + self._buf.append(line) + + def endPara(self): + #ends the current paragraph, or preformatted block + + text = string.join(self._buf, ' ') + if text: + if self._mode == PREFORMATTED: + #item 3 is list of lines + self._results.append(('Preformatted', self._style, + string.join(self._buf,'\n'))) + else: + self._results.append(('Paragraph', self._style, text)) + self._buf = [] + self._style = 'Normal' + + def beginPre(self, stylename): + self._mode = PREFORMATTED + self._style = stylename + + def endPre(self): + self.endPara() + self._mode = PLAIN + + def image(self, filename): + self.endPara() + self._results.append(('Image', filename)) + + def vSpace(self, points): + """Inserts a vertical spacer""" + self._results.append(('VSpace', points)) + + def pageBreak(self): + """Inserts a frame break""" + self._results.append(('PageBreak','blah')) # must be a tuple + + def custom(self, moduleName, funcName): + """Goes and gets the Python object and adds it to the story""" + self.endPara() + self._results.append(('Custom',moduleName, funcName)) + + + + def getModuleDoc(self, modulename, pathname=None): + """Documents the entire module at this point by making + paragraphs and preformatted objects""" + docco = codegrab.getObjectsDefinedIn(modulename, pathname) + if docco.doc <> None: + self._results.append(('Paragraph', 'DocString', docco.doc)) + if len(docco.functions) > 0: + for fn in docco.functions: + if fn.status == 'official': + self._results.append(('Preformatted','FunctionHeader', fn.proto)) + self._results.append(('Preformatted','DocString', fn.doc)) + + if len(docco.classes) > 0: + for cls in docco.classes: + if cls.status == 'official': + self._results.append(('Preformatted','FunctionHeader', 'Class %s:' % cls.name)) + self._results.append(('Preformatted','DocString', cls.doc)) + for mth in cls.methods: + if mth.status == 'official': + self._results.append(('Preformatted','FunctionHeader', mth.proto)) + self._results.append(('Preformatted','DocStringIndent', mth.doc)) + + + def getClassDoc(self, modulename, classname, pathname=None): + """Documents the class and its public methods""" + docco = codegrab.getObjectsDefinedIn(modulename, pathname) + found = 0 + for cls in docco.classes: + if cls.name == classname: + found = 1 + self._results.append(('Preformatted','FunctionHeader', 'Class %s:' % cls.name)) + self._results.append(('Preformatted','DocString', cls.doc)) + for mth in cls.methods: + if mth.status == 'official': + self._results.append(('Preformatted','FunctionHeader', mth.proto)) + self._results.append(('Preformatted','DocStringIndent', mth.doc)) + break + assert found, 'No Classes Defined in ' + modulename + + def nextPageTemplate(self, templateName): + self._results.append(('NextPageTemplate',templateName)) + +if __name__=='__main__': #NORUNTESTS + if len(sys.argv) <> 2: + print 'usage: yaml.py source.txt' + else: + p = Parser() + results = p.parseFile(sys.argv[1]) + import pprint + pprint.pprint(results) \ No newline at end of file diff --git a/bin/reportlab/tools/docco/yaml2pdf.py b/bin/reportlab/tools/docco/yaml2pdf.py new file mode 100644 index 00000000000..454bf53cf71 --- /dev/null +++ b/bin/reportlab/tools/docco/yaml2pdf.py @@ -0,0 +1,104 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/yaml2pdf.py +# yaml2pdf - turns stuff in Yet Another Markup Language +# into PDF documents. Very crude - it assumes a +# doc template and stylesheet (hard coded for now) +# and basically cranks out paragraphs in each style +"""yaml2pdf.py - converts Yet Another Markup Language +to reasonable PDF documents. This is ReportLab's +basic documentation tool. + +Usage: +. "yaml2pdf.py filename.ext" will create "filename.pdf" +""" + +import sys +import os +import imp + +import yaml +from rltemplate import RLDocTemplate +from reportlab.lib.styles import ParagraphStyle +from reportlab.lib.enums import * +from reportlab.lib.pagesizes import A4 +from reportlab.platypus import * +from reportlab.lib import colors +from reportlab.lib.units import inch + + +from stylesheet import getStyleSheet + + +def run(infilename, outfilename): + p = yaml.Parser() + results = p.parseFile(infilename) + + ss = getStyleSheet() + + #now make flowables from the results + story = [] + for thingy in results: + typ = thingy[0] + if typ == 'Paragraph': + (typ2, stylename, text) = thingy + if stylename == 'bu': + bulletText='\267' + else: + bulletText=None + try: + style = ss[stylename] + except KeyError: + print 'Paragraph style "%s" not found in stylesheet, using Normal instead' % stylename + style = ss['Normal'] + story.append(Paragraph(text, style, bulletText=bulletText)) + elif typ == 'Preformatted': + (typ2, stylename, text) = thingy + try: + style = ss[stylename] + except KeyError: + print 'Preformatted style "%s" not found in stylesheet, using Normal instead' % stylename + style = ss['Normal'] + story.append(Preformatted(text, style, bulletText=bulletText)) + elif typ == 'Image': + filename = thingy[1] + img = Image(filename) + story.append(img) + elif typ == 'PageBreak': + story.append(PageBreak()) + elif typ == 'VSpace': + height = thingy[1] + story.append(Spacer(0, height)) + elif typ == 'NextPageTemplate': + story.append(NextPageTemplate(thingy[1])) + elif typ == 'Custom': + # go find it + searchPath = [os.getcwd()+'\\'] + (typ2, moduleName, funcName) = thingy + found = imp.find_module(moduleName, searchPath) + assert found, "Custom object module %s not found" % moduleName + (file, pathname, description) = found + mod = imp.load_module(moduleName, file, pathname, description) + + #now get the function + func = getattr(mod, funcName) + story.append(func()) + + else: + print 'skipping',typ, 'for now' + + + #print it + doc = RLDocTemplate(outfilename, pagesize=A4) + doc.build(story) + +if __name__ == '__main__': #NORUNTESTS + if len(sys.argv) == 2: + infilename = sys.argv[1] + outfilename = os.path.splitext(infilename)[0] + '.pdf' + if os.path.isfile(infilename): + run(infilename, outfilename) + else: + print 'File not found %s' % infilename + else: + print __doc__ \ No newline at end of file diff --git a/bin/reportlab/tools/py2pdf/README b/bin/reportlab/tools/py2pdf/README new file mode 100644 index 00000000000..9a3e9c8fb7e --- /dev/null +++ b/bin/reportlab/tools/py2pdf/README @@ -0,0 +1,8 @@ +py2pdf - converts Python source code to PDF +with a LOT of options. + +See the header of py2pdf.py for full details. +execute demo.py to see some output. + +Contributed by Dinu Gherman and copyrighted by him. +Uses Just van Rossum's PyFontify. diff --git a/bin/reportlab/tools/py2pdf/__init__.py b/bin/reportlab/tools/py2pdf/__init__.py new file mode 100644 index 00000000000..e0226c4a6fd --- /dev/null +++ b/bin/reportlab/tools/py2pdf/__init__.py @@ -0,0 +1,3 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/py2pdf/__init__.py diff --git a/bin/reportlab/tools/py2pdf/demo-config.txt b/bin/reportlab/tools/py2pdf/demo-config.txt new file mode 100644 index 00000000000..4c5ed1a314b --- /dev/null +++ b/bin/reportlab/tools/py2pdf/demo-config.txt @@ -0,0 +1,11 @@ +--bgCol=(1,.9,.9) +--lineNum +#--fontSize=12 +#--paperFormat=B5 +#--fontName=Helvetica +#--landscape +#--restCol=(0,1,0) +#--mode=mono +--kwCol=(1,0,1) +#--kwCol=#dd00ff +#--kwCol=(.5,.5,.5) diff --git a/bin/reportlab/tools/py2pdf/demo.py b/bin/reportlab/tools/py2pdf/demo.py new file mode 100644 index 00000000000..aa69f8cf042 --- /dev/null +++ b/bin/reportlab/tools/py2pdf/demo.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python + +"""demo.py - Demo script for py2pdf 0.5. + +The main idea is: take one Python file and make a whole +bunch of PDFs out of it for test purposes. + +Dinu Gherman +""" + + +import string, re, os, os.path, sys, shutil +from py2pdf import * + + +### Custom layouter class used with test3(). + +class ImgPDFLayouter (PythonPDFLayouter): + "A custom layouter displaying an image on each page." + + def setMainFrame(self, frame=None): + "Make a frame in the right half of the page." + + width, height = self.options.realPaperFormat.size + self.frame = height - 2*cm, 2*cm, 250, width-1*cm + + self.makeForm() + + + def makeForm(self): + "Use the experimental ReportLab form support." + + width, height = self.options.realPaperFormat.size + tm, bm, lm, rm = self.frame + c = self.canvas + + # Define a PDF form containing an image frame + # that will be included on every page, but + # stored only once in the resulting file. + c.beginForm("imageFrame") + c.saveState() + x, y = 219.0, 655.0 # Known size of the picture. + c.scale((lm - 1*cm)/x, height/y) + path = 'vertpython.jpg' + c.drawImage(path, 0, 0) + c.restoreState() + c.endForm() + + + def putPageDecoration(self): + "Draw the left border image and page number." + + width, height = self.options.realPaperFormat.size + tm, bm, lm, rm = self.frame + c = self.canvas + + # Footer. + x, y = lm + 0.5 * (rm - lm), 0.5 * bm + c.setFillColor(Color(0, 0, 0)) + c.setFont('Times-Italic', 12) + label = "Page %d" % self.pageNum + c.drawCentredString(x, y, label) + + # Call the previously stored form. + c.doForm("imageFrame") + + +### Helpers. + +def modifyPath(path, new, ext='.py'): + "Modifying the base name of a file." + + rest, ext = os.path.splitext(path) + path, base = os.path.split(rest) + format = "%s-%s%s" % (base, new, ext) + return os.path.join(path, format) + + +def getAllTestFunctions(): + "Return a list of all test functions available." + + globs = globals().keys() + tests = filter(lambda g: re.match('test[\d]+', g), globs) + tests.sort() + return map(lambda t: globals()[t], tests) + + +### Test functions. +### +### In order to be automatically found and applied to +### a Python file all test functions must follow the +### following naming pattern: 'test[0-9]+' and contain +### a doc string. + +def test0(path): + "Creating a PDF assuming an ASCII file." + + p = PDFPrinter() + p.process(path) + + +def test1(path): + "Creating a PDF using only default options." + + p = PythonPDFPrinter() + p.process(path) + + +def test2(path): + "Creating a PDF with some modified options." + + p = PythonPDFPrinter() + p.options.updateOption('landscape', 1) + p.options.updateOption('fontName', 'Helvetica') + p.options.updateOption('fontSize', '14') + p.options.display() + p.process(path) + + +def test3(path): + "Creating several PDFs as 'magazine listings'." + + p = PythonPDFPrinter() + p.Layouter = EmptyPythonPDFLayouter + p.options.updateOption('paperSize', '(250,400)') + p.options.updateOption('multiPage', 1) + p.options.updateOption('lineNum', 1) + p.process(path) + + +def test4(path): + "Creating a PDF in monochrome mode." + + p = PythonPDFPrinter() + p.options.updateOption('mode', 'mono') + p.process(path) + + +def test5(path): + "Creating a PDF with options from a config file." + + p = PythonPDFPrinter() + i = string.find(path, 'test5') + newPath = modifyPath(path[:i-1], 'config') + '.txt' + + try: + p.options.updateWithContentsOfFile(newPath) + p.options.display() + p.process(path) + except IOError: + print "Skipping test5() due to IOError." + + +def test6(path): + "Creating a PDF with modified layout." + + p = PythonPDFPrinter() + p.Layouter = ImgPDFLayouter + p.options.updateOption('fontName', 'Helvetica') + p.options.updateOption('fontSize', '12') + p.options.display() + p.process(path) + + +### Main. + +def main(inPath, *tests): + "Apply various tests to one Python source file." + + for t in tests: + newPath = modifyPath(inPath, t.__name__) + shutil.copyfile(inPath, newPath) + try: + print t.__doc__ + t(newPath) + finally: + os.remove(newPath) + print + + +if __name__=='__main__': + # Usage: "python demo.py [ ...]" + try: + try: + tests = map(lambda a: globals()[a], sys.argv[2:]) + except IndexError: + tests = getAllTestFunctions() + + fileName = sys.argv[1] + apply(main, [fileName]+tests) + + # Usage: "python demo.py" (implicitly does this: + # "python demo.py demo.py" ) + except IndexError: + print "Performing self-test..." + fileName = sys.argv[0] + tests = getAllTestFunctions() + apply(main, [fileName]+tests) diff --git a/bin/reportlab/tools/py2pdf/idle_print.py b/bin/reportlab/tools/py2pdf/idle_print.py new file mode 100644 index 00000000000..18b3080a259 --- /dev/null +++ b/bin/reportlab/tools/py2pdf/idle_print.py @@ -0,0 +1,56 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/py2pdf/idle_print.py + +# idle_print [py2pdf_options] filename +__version__=''' $Id: idle_print.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' +# you should adjust the globals below to configure for your system + +import sys, os, py2pdf, string, time +#whether we remove input/output files; if you get trouble on windows try setting _out to 0 +auto_rm_in = 1 +auto_rm_out = 1 +viewOnly = 0 + +#how to call up your acrobat reader +if sys.platform=='win32': + acrord = 'C:\\Program Files\\Adobe\\Acrobat 4.0\\Reader\\AcroRd32.exe' + def printpdf(pdfname): + args = [acrord, pdfname] + os.spawnv(os.P_WAIT, args[0], args) +else: + acrord = 'acroread' + def printpdf(pdfname): + if viewOnly: + cmd = "%s %s" % (acrord,pdfname) + else: + cmd = "%s -toPostScript < %s | lpr" % (acrord,pdfname) + os.system(cmd) + +args = ['--input=python'] +files = [] +for f in sys.argv[1:]: + if f[:2]=='--': + opt = f[2:] + if opt =='no_auto_rm_in': + auto_rm_in = 0 + elif opt =='auto_rm_in': + auto_rm_in = 1 + elif opt =='no_auto_rm_out': + auto_rm_out = 0 + elif opt =='auto_rm_out': + auto_rm_out = 1 + elif opt =='viewonly': + viewOnly = 1 + elif opt[:9] =='acroread=': + acrord = opt[9:] + else: + args.append(f) + else: files.append(f) + +for f in files: + py2pdf.main(args+[f]) + if auto_rm_in: os.remove(f) + pdfname = os.path.splitext(f)[0]+'.pdf' + printpdf(pdfname) + if auto_rm_out: os.remove(pdfname) \ No newline at end of file diff --git a/bin/reportlab/tools/py2pdf/py2pdf.py b/bin/reportlab/tools/py2pdf/py2pdf.py new file mode 100644 index 00000000000..faaeacbc21f --- /dev/null +++ b/bin/reportlab/tools/py2pdf/py2pdf.py @@ -0,0 +1,1567 @@ +#!/usr/bin/env python + +""" Python Highlighter for PDF Version: 0.6 + + py2pdf.py [options] [ ...] + + options: + -h or --help print help (this message) + - read from stdin (writes to stdout) + --stdout read from file, write to stdout + (restricted to first file only) + --title= specify title + --config=<file> read configuration options from <file> + --input=<type> set input file type + 'python': Python code (default) + 'ascii': arbitrary ASCII files (b/w) + --mode=<mode> set output mode + 'color': output in color (default) + 'mono': output in b/w + --paperFormat= set paper format (ISO A, B, C series, + <format> US legal & letter; default: 'A4') + e.g. 'letter', 'A3', 'A4', 'B5', 'C6', ... + --paperSize= set paper size in points (size being a valid + <size> numeric 2-tuple (x,y) w/o any whitespace) + --landscape set landscape format (default: portrait) + --bgCol=<col> set page background-color in hex code like + '#FFA024' or '0xFFA024' for RGB components + (overwrites mono mode) + --<cat>Col=<col> set color of certain code categories, i.e. + <cat> can be the following: + 'comm': comments + 'ident': identifiers + 'kw': keywords + 'strng': strings + 'param': parameters + 'rest': all the rest + --fontName=<name> set base font name (default: 'Courier') + like 'Helvetica', 'Times-Roman' + --fontSize=<size> set font size (default: 8) + --tabSize=<size> set tab size (default: 4) + --lineNum print line numbers + --multiPage generate one file per page (with filenames + tagged by 1, 2...), disables PDF outline + --noOutline don't generate PDF outline (default: unset) + -v or --verbose set verbose mode + + Takes the input, assuming it is Python source code and formats + it into PDF. + + * Uses Just van Rossum's PyFontify version 0.4 to tag Python + scripts, now included in reportlab.lib. + + * Uses the ReportLab library (version 0.92 or higher) to + generate PDF. You can get it without charge from ReportLab: + http://www.reportlab.com + + * Parts of this code still borrow heavily from Marc-Andre + Lemburg's py2html who has kindly given permission to + include them in py2pdf. Thanks, M.-A.! +""" + +__copyright__ = """ +---------------------------------------------------------------------- +(c) Copyright by Dinu C. Gherman, 2000 (gherman@europemail.com) + + Permission to use, copy, modify, and distribute this + software and its documentation for any purpose and + without fee or royalty is hereby granted, provided + that the above copyright notice appear in all copies + and that both that copyright notice and this permission + notice appear in supporting documentation or portions + thereof, including modifications, that you make. + + THE AUTHOR DINU C. GHERMAN DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT + SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE + OR PERFORMANCE OF THIS SOFTWARE! +""" + +__version__ = '0.6' +__author__ = 'Dinu C. Gherman' +__date__ = '2001-08-17' +__url__ = 'http://starship.python.net/crew/gherman/programs/py2pdf' + + +import sys, string, re, os, getopt + +from reportlab.pdfgen import canvas +from reportlab.lib.colors import Color, HexColor +from reportlab.lib import fonts +from reportlab.lib.units import cm, inch + + +### Helpers functions. + +def makeTuple(aString): + """Evaluate a string securely into a tuple. + + Match the string and return an evaluated object thereof if and + only if we think it is a tuple of two or more numeric decimal(!) + values, integers or floats. E-notation, hex or octal is not + supported, though! Shorthand notation (omitting leading or trail- + zeros, before or after the decimal point) like .25 or 25. is + supported. + """ + + c = string.count(aString, ',') + num = '(\d*?\.?\d+?)|(\d+?\.?\d*?)' + tup = string.join([num]*c, ',') + + if re.match('\(' + tup + '\)', aString): + return eval(aString) + else: + details = '%s cannot be parsed into a numeric tuple!' % aString + raise 'ValueError', details + + +def loadFontifier(options=None): + "Load a tagging module and return a corresponding function." + + # We can't use 'if options' because of the modified + # attribute lookup it seems. + + if type(options) != type(None) and options.marcs: + # Use mxTextTool's tagging engine. + + from mxTextTools import tag + from mxTextTools.Examples.Python import python_script + tagFunc = lambda text, tag=tag, pytable=python_script: \ + tag(text,pytable)[1] + + else: + # Load Just's. + + try: + from reportlab.lib import PyFontify + + if PyFontify.__version__ < '0.3': + raise ValueError + + tagFunc = PyFontify.fontify + + except: + print """ + Sorry, but this script needs the PyFontify.py module version 0.3 + or higher; You can download it from Just's homepage at + + URL: http://starship.python.net/crew/just +""" + sys.exit() + + return tagFunc + + +def makeColorFromString(aString): + """Convert a string to a ReportLab RGB color object. + + Supported formats are: '0xFFFFFF', '#FFFFFF' and '(R,G,B)' + where the latter is a tuple of three floats in the range + [0.0, 1.0]. + """ + + s = aString + + if s[0] == '#' or s[:2] in ('0x', '0X'): + if s[:2] in ('0x', '0X'): + return HexColor('#' + s[2:]) + + elif s[0] == '(': + r, g, b = makeTuple(aString) + return Color(r, g, b) + + +### Utility classes. + +# For py2pdf this is some kind of overkill, but it's fun, too. +class PaperFormat: + """Class to capture a paper format/size and its orientation. + + This class represents an abstract paper format, including + the size and orientation of a sheet of paper in some formats + plus a few operations to change its size and orientation. + """ + + _A4W, _A4H = 21*cm, 29.7*cm + A0 = (4*_A4W, 4*_A4H) + + _B4W, _B4H = 25*cm, 35.3*cm + B0 = (4*_B4W, 4*_B4H) + + _C4W, _C4H = 22.9*cm, 32.4*cm + C0 = (4*_C4W, 4*_C4H) + + letter = (8.5*inch, 11*inch) + legal = (8.5*inch, 14*inch) + + + def __init__(self, nameOrSize='A4', landscape=0): + "Initialisation." + + t = type(nameOrSize) + + if t == type(''): + self.setFormatName(nameOrSize, landscape) + elif t == type((1,)): + self.setSize(nameOrSize) + self.setLandscape(landscape) + + + def __repr__(self): + """Return a string representation of ourself. + + The returned string can also be used to recreate + the same PaperFormat object again. + """ + + if self.name != 'custom': + nos = `self.name` + else: + nos = `self.size` + + format = "PaperFormat(nameOrSize=%s, landscape=%d)" + tuple = (nos, self.landscape) + + return format % tuple + + + def setSize(self, size=None): + "Set explicit paper size." + + self.name = 'custom' + x, y = self.size = size + self.landscape = x < y + + + def setLandscape(self, flag): + "Set paper orientation as desired." + + # Swap paper orientation if needed. + + self.landscape = flag + x, y = self.size + + ls = self.landscape + if (ls and x < y) or (not ls and x > y): + self.size = y, x + + + def setFormatName(self, name='A4', landscape=0): + "Set paper size derived from a format name." + + if name[0] in 'ABC': + # Assume ISO-A, -B, -C series. + # (Hmm, are B and C really ISO standards + # or just DIN? Well...) + c, f = name[0], int(name[1:]) + self.size = getattr(self, c + '0') + self.name = c + '0' + self.makeHalfSize(f) + + elif name == 'letter': + self.size = self.letter + self.name = 'letter' + + elif name == 'legal': + self.size = self.legal + self.name = 'legal' + + self.setLandscape(landscape) + + + def makeHalfSize(self, times=1): + "Reduce paper size (surface) by 50% multiple times." + + # Orientation remains unchanged. + + # Iterates only for times >= 1. + for i in xrange(times): + s = self.size + self.size = s[1] / 2.0, s[0] + + if self.name[0] in 'ABC': + self.name = self.name[0] + `int(self.name[1:]) + 1` + else: + self.name = 'custom' + + + def makeDoubleSize(self, times=1): + "Increase paper size (surface) by 50% multiple times." + + # Orientation remains unchanged. + + # Iterates only for times >= 1. + for i in xrange(times): + s = self.size + self.size = s[1], s[0] * 2.0 + + if self.name[0] in 'ABC': + self.name = self.name[0] + `int(self.name[1:]) - 1` + else: + self.name = 'custom' + + +class Options: + """Container class for options from command line and config files. + + This class is a container for options as they are specified + when a program is called from a command line, but also from + the same kind of options that are saved in configuration + files. Both the short UNIX style (e.g. '-v') as well as the + extended GNU style (e.g. '--help') are supported. + + An 'option' is a <name>/<value> pair with both parts being + strings at the beginning, but where <value> might be conver- + ted later into any other Python object. + + Option values can be accessed by using their name as an attri- + bute of an Options object, returning None if no such option + name exists (that makes None values impossible, but so what?). + """ + + # Hmm, could also use UserDict... maybe later. + + def __init__(self): + "Initialize with some default options name/values." + + # Hmm, could also pass an initial optional dict... + + # Create a dictionary containing the options + # and populate it with defaults. + self.pool = {} + self.setDefaults() + + + def __getattr__(self, name): + "Turn attribute access into dictionary lookup." + + return self.pool.get(name) + + + def setDefaults(self): + "Set default options." + + ### Maybe get these from a site config file... + self.pool.update({'fontName' : 'Courier', + 'fontSize' : 8, + 'bgCol' : Color(1, 1, 1), + 'mode' : 'color', + 'lineNum' : 0, + 'tabSize' : 4, + 'paperFormat': 'A4', + 'landscape' : 0, + 'title' : None, + 'multiPage' : 0}) + + # Default colors (for color mode), mostly taken from py2html. + self.pool.update({'commCol' : HexColor('#1111CC'), + 'kwCol' : HexColor('#3333CC'), + 'identCol' : HexColor('#CC0000'), + 'paramCol' : HexColor('#000066'), + 'strngCol' : HexColor('#119911'), + 'restCol' : HexColor('#000000')}) + + # Add a default 'real' paper format object. + pf = PaperFormat(self.paperFormat, self.landscape) + self.pool.update({'realPaperFormat' : pf}) + self.pool.update({'files' : []}) + + + def display(self): + "Display all current option names and values." + + self.saveToFile(sys.stdout) + + + def saveToFile(self, path): + "Save options as a log file." + + if type(path) == type(''): + f = open(path, 'w') + else: + # Assume a file-like object. + f = path + + items = self.pool.items() + items.sort() + + for n, v in items: + f.write("%-15s : %s\n" % (n, `v`)) + + + def updateWithContentsOfFile(self, path): + """Update options as specified in a config file. + + The file is expected to contain one option name/value pair + (seperated by an equal sign) per line, but no other whitespace. + Option values may contain equal signs, but no whitespace. + Option names must be valid Python strings, preceeded by one + or two dashes. + """ + + config = open(path).read() + config = string.split(config, '\n') + + for cfg in config: + if cfg == None: + break + + if cfg == '' or cfg[0] == '#': + continue + + # GNU long options + if '=' in cfg and cfg[:2] == '--': + p = string.find(cfg, '=') + opt, arg = cfg[2:p], cfg[p+1:] + self.updateOption(opt, arg) + + # Maybe treat single-letter options as well? + # elif ':' in cfg and cfg[0] == '-' and cfg[1] != '-': + # pass + + else: + self.updateOption(cfg[2:], None) + + + def updateWithContentsOfArgv(self, argv): + "Update options as specified in a (command line) argument vector." + + # Specify accepted short option names (UNIX style). + shortOpts = 'hv' + + # Specify accepted long option names (GNU style). + lo = 'tabSize= paperFormat= paperSize= landscape stdout title= fontName= fontSize=' + lo = lo + ' bgCol= verbose lineNum marcs help multiPage noOutline config= input= mode=' + lo = lo + ' commCol= identCol= kwCol= strngCol= paramCol= restCol=' + longOpts = string.split(lo, ' ') + + try: + optList, args = getopt.getopt(argv, shortOpts, longOpts) + except getopt.error, msg: + sys.stderr.write("%s\nuse -h or --help for help\n" % str(msg)) + sys.exit(2) + + self.updateOption('files', args) # Hmm, really needed? + + for o, v in optList: + # Remove leading dashes (max. two). + if o[0] == '-': + o = o[1:] + if o[0] == '-': + o = o[1:] + + self.updateOption(o, v) + + + def updateOption(self, name, value): + "Update an option from a string value." + + # Special treatment for coloring options... + if name[-3:] == 'Col': + if name[:-3] in string.split('bg comm ident kw strng param rest', ' '): + self.pool[name] = makeColorFromString(value) + + elif name == 'paperSize': + tup = makeTuple(value) + self.pool['paperSize'] = tup + if not self.realPaperFormat: + pf = PaperFormat(self.paperFormat, self.landscape) + self.pool['realPaperFormat'] = pf + self.pool['realPaperFormat'].setSize(tup) + + elif name == 'paperFormat': + self.pool['paperFormat'] = value + if not self.realPaperFormat: + pf = PaperFormat(self.paperFormat, self.landscape) + self.pool['realPaperFormat'] = pf + self.pool['realPaperFormat'].setFormatName(self.paperFormat, self.landscape) + + elif name == 'landscape': + self.pool['landscape'] = 1 + if self.realPaperFormat: + self.pool['realPaperFormat'].setLandscape(1) + + elif name == 'fontSize': + self.pool['fontSize'] = int(value) + + elif name == 'tabSize': + self.pool['tabSize'] = int(value) + + elif name == 'mode': + self.pool['mode'] = value + if value == 'mono': + cats = 'comm ident kw strng param rest' + for cat in string.split(cats, ' '): + self.pool[cat + 'Col'] = Color(0, 0, 0) + + # Parse configuration file... + elif name == 'config': + self.updateWithContentsOfFile(value) + + elif name == 'stdout': + self.pool['stdout'] = 1 + + elif name == 'files': + self.pool['files'] = value + + else: + # Set the value found or 1 for options without values. + self.pool[name] = value or 1 + + + def update(self, **options): + "Update options." + + # Not much tested and/or used, yet!! + + for n, v in options.items(): + self.pool[n] = v + + +### Layouting classes. + +class PDFLayouter: + """A class to layout a simple PDF document. + + This is intended to help generate PDF documents where all pages + follow the same kind of 'template' which is supposed to be the + same adornments (header, footer, etc.) on each page plus a main + 'frame' on each page. These frames are 'connected' such that one + can add individual text lines one by one with automatic line + wrapping, page breaks and text flow between frames. + """ + + def __init__(self, options): + "Initialisation." + + self.options = options + self.canvas = None + self.multiLineStringStarted = 0 + self.lineNum = 0 + + # Set a default color and font. + o = self.options + self.currColor = o.restCol + self.currFont = (o.fontName, o.fontSize) + + + ### Helper methods. + + def setMainFrame(self, frame=None): + "Define the main drawing frame of interest for each page." + + if frame: + self.frame = frame + else: + # Maybe a long-term candidate for additional options... + width, height = self.options.realPaperFormat.size + self.frame = height - 3*cm, 3*cm, 2*cm, width - 2*cm + + # self.frame is a 4-tuple: + # (topMargin, bottomMargin, leftMargin, rightMargin) + + + def setPDFMetaInfo(self): + "Set PDF meta information." + + o = self.options + c = self.canvas + c.setAuthor('py2pdf %s' % __version__) + c.setSubject('') + + # Set filename. + filename = '' + + # For stdin use title option or empty... + if self.srcPath == sys.stdin: + if o.title: + filename = o.title + # otherwise take the input file's name. + else: + path = os.path.basename(self.srcPath) + filename = o.title or path + + c.setTitle(filename) + + + def setFillColorAndFont(self, color, font): + "Set new color/font (maintaining the current 'state')." + + self.currFont = font + self.currColor = color + + fontName, fontSize = font + self.text.setFont(fontName, fontSize) + self.text.setFillColor(color) + + + ### API + + def begin(self, srcPath, numLines): + "Things to do before doing anything else." + + self.lineNum = 0 + self.pageNum = 0 + self.numLines = numLines + self.srcPath = srcPath + + # Set output filename (stdout if desired). + o = self.options + if o.stdout: + self.pdfPath = sys.stdout + else: + if srcPath != sys.stdin: + self.pdfPath = os.path.splitext(srcPath)[0] + '.pdf' + else: + self.pdfPath = sys.stdout + + + def beginDocument(self): + """Things to do when a new document should be started. + + The initial page counter is 0, meaning that beginPage() + will be called by beginLine()... + """ + + # Set initial page number and store file name. + self.pageNum = 0 + o = self.options + + if not o.multiPage: + # Create canvas. + size = o.realPaperFormat.size + self.canvas = canvas.Canvas(self.pdfPath, size, verbosity=0) + c = self.canvas + c.setPageCompression(1) + c.setFont(o.fontName, o.fontSize) + + # Create document meta information. + self.setPDFMetaInfo() + + # Set drawing frame. + self.setMainFrame() + + # Determine the left text margin by adding the with + # of the line number to the left margin of the main frame. + format = "%%%dd " % len(`self.numLines`) + fn, fs = self.currFont + text = format % self.lineNum + tm, bm, lm, rm = self.frame + self.txm = lm + if o.lineNum: + self.txm = self.txm + c.stringWidth(text, fn, fs) + + + def beginPage(self): + "Things to do when a new page has to be added." + + o = self.options + self.pageNum = self.pageNum + 1 + + if not o.multiPage: + tm, bm, lm, rm = self.frame + self.text = self.canvas.beginText(lm, tm - o.fontSize) + self.setFillColorAndFont(self.currColor, self.currFont) + else: + # Fail if stdout desired (with multiPage). + if o.stdout: + raise "IOError", "Can't create multiple pages on stdout!" + + # Create canvas with a modified path name. + base, ext = os.path.splitext(self.pdfPath) + newPath = "%s-%d%s" % (base, self.pageNum, ext) + size = o.realPaperFormat.size + self.canvas = canvas.Canvas(newPath, size, verbosity=0) + c = self.canvas + c.setPageCompression(1) + c.setFont(o.fontName, o.fontSize) + + # Create document meta information. + self.setPDFMetaInfo() + + # Set drawing frame. + self.setMainFrame() + + tm, bm, lm, rm = self.frame + self.text = self.canvas.beginText(lm, tm - o.fontSize) + self.setFillColorAndFont(self.currColor, self.currFont) + + self.putPageDecoration() + + + def beginLine(self, wrapped=0): + "Things to do when a new line has to be added." + + # If there is no page yet, create the first one. + if self.pageNum == 0: + self.beginPage() + + # If bottom of current page reached, do a page break. + # (This works only with one text object being used + # for the entire page. Otherwise we need to maintain + # the vertical position of the current line ourself.) + y = self.text.getY() + tm, bm, lm, rm = self.frame + if y < bm: + self.endPage() + self.beginPage() + + # Print line number label, if needed. + o = self.options + if o.lineNum: + #self.putLineNumLabel() + font = ('Courier', o.fontSize) + + if not wrapped: + # Print a label containing the line number. + self.setFillColorAndFont(o.restCol, font) + format = "%%%dd " % len(`self.numLines`) + self.text.textOut(format % self.lineNum) + else: + # Print an empty label (using bgCol). Hackish! + currCol = self.currColor + currFont = self.currFont + self.setFillColorAndFont(o.bgCol, font) + self.text.textOut(' '*(len(`self.numLines`) + 1)) + self.setFillColorAndFont(currCol, currFont) + + + def endLine(self, wrapped=0): + "Things to do after a line is basically done." + + # End the current line by adding an 'end of line'. + # (Actually done by the text object...) + self.text.textLine('') + + if not wrapped: + self.lineNum = self.lineNum + 1 + + + def endPage(self): + "Things to do after a page is basically done." + + c = self.canvas + + # Draw the current text object (later we might want + # to do that after each line...). + c.drawText(self.text) + c.showPage() + + if self.options.multiPage: + c.save() + + + def endDocument(self): + "Things to do after the document is basically done." + + c = self.canvas + + # Display rest of last page and save it. + c.drawText(self.text) + c.showPage() + c.save() + + + def end(self): + "Things to do after everything has been done." + + pass + + + ### The real meat: methods writing something to a canvas. + + def putLineNumLabel(self, text, wrapped=0): + "Add a long text that can't be split into chunks." + + o = self.options + font = ('Courier', o.fontSize) + + if not wrapped: + # Print a label containing the line number. + self.setFillColorAndFont(o.restCol, font) + format = "%%%dd " % len(`self.numLines`) + self.text.textOut(format % self.lineNum) + else: + # Print an empty label (using bgCol). Hackish! + currCol = self.currColor + currFont = self.currFont + self.setFillColorAndFont(o.bgCol, font) + self.text.textOut(' '*(len(`self.numLines`) + 1)) + self.setFillColorAndFont(currCol, currFont) + + + # Tried this recursively before, in order to determine + # an appropriate string limit rapidly, but this wasted + # much space and showed very poor results... + # This linear method is very slow, but such lines should + # be very rare, too! + + def putSplitLongText(self, text): + "Add a long text that can't be split into chunks." + + # Now, the splitting will be with 'no mercy', + # at the right margin of the main drawing area. + + M = len(text) + t = self.text + x = t.getX() + o = self.options + tm, bm, lm, rm = self.frame + + width = self.canvas.stringWidth + fn, fs = self.currFont + tw = width(text, fn, fs) + + tx = self.text + if tw > rm - lm - x: + i = 1 + T = '' + while text: + T = text[:i] + tx = self.text # Can change after a page break. + tw = width(T, fn, fs) + + if x + tw > rm: + tx.textOut(T[:-1]) + self.endLine(wrapped=1) + self.beginLine(wrapped=1) + x = tx.getX() + text = text[i-1:] + M = len(text) + i = 0 + + i = i + 1 + + if i > M: + break + + tx.textOut(T) + + else: + t.textOut(text) + + + def putLongText(self, text): + "Add a long text by gracefully splitting it into chunks." + + # Splitting is currently done only at blanks, but other + # characters such as '.' or braces are also good + # possibilities... later... + + o = self.options + tm, bm, lm, rm = self.frame + + width = self.canvas.stringWidth + fn, fs = self.currFont + tw = width(text, fn, fs) + + arr = string.split(text, ' ') + + for i in range(len(arr)): + a = arr[i] + t = self.text # Can change during the loop... + tw = width(a, fn, fs) + x = t.getX() + + # If current item does not fit on current line, have it + # split and then put before/after a line (maybe also + # page) break. + if x + tw > rm: + self.putSplitLongText(a) + t = self.text # Can change after a page break... + + # If it fits, just add it to the current text object. + else: + t.textOut(a) + + # Add the character we used to split th original text. + if i < len(arr) - 1: + t.textOut(' ') + + + def putText(self, text): + "Add some text to the current line." + + t = self.text + x = t.getX() + o = self.options + fn, fs = o.fontName, o.fontSize + tw = self.canvas.stringWidth(text, fn, fs) + rm = self.frame[3] + + if x + tw < rm: + t.textOut(text) + else: + self.putLongText(text) + + + # Not yet tested. + def putLine(self, text): + "Add a line to the current text." + + self.putText(text) + self.endLine() + + + def putPageDecoration(self): + "Draw some decoration on each page." + + # Use some abbreviations. + o = self.options + c = self.canvas + tm, bm, lm, rm = self.frame + + # Restore default font. + c.setFont(o.fontName, o.fontSize) + + c.setLineWidth(0.5) # in pt. + + # Background color. + c.setFillColor(o.bgCol) + pf = o.realPaperFormat.size + c.rect(0, 0, pf[0], pf[1], stroke=0, fill=1) + + # Header. + c.setFillColorRGB(0, 0, 0) + c.line(lm, tm + .5*cm, rm, tm + .5*cm) + c.setFont('Times-Italic', 12) + + if self.pdfPath == sys.stdout: + filename = o.title or ' ' + else: + path = os.path.basename(self.srcPath) + filename = o.title or path + + c.drawString(lm, tm + 0.75*cm + 2, filename) + + # Footer. + c.line(lm, bm - .5*cm, rm, bm - .5*cm) + c.drawCentredString(0.5 * pf[0], 0.5*bm, "Page %d" % self.pageNum) + + # Box around main frame. + # c.rect(lm, bm, rm - lm, tm - bm) + + +class PythonPDFLayouter (PDFLayouter): + """A class to layout a simple multi-page PDF document. + """ + + ### API for adding specific Python entities. + + def addKw(self, t): + "Add a keyword." + + o = self.options + + # Make base font bold. + fam, b, i = fonts.ps2tt(o.fontName) + ps = fonts.tt2ps(fam, 1, i) + font = (ps, o.fontSize) + + self.setFillColorAndFont(o.kwCol, font) + + # Do bookmarking... + if not o.noOutline and not o.multiPage: + if t in ('class', 'def'): + tm, bm, lm, rm = self.frame + pos = self.text.getX() + + if pos == self.txm: + self.startPositions = [] + + self.startPos = pos + + if not hasattr(self, 'startPositions'): + self.startPositions = [] + + if pos not in self.startPositions: + self.startPositions.append(pos) + + # Memorize certain keywords. + self.itemFound = t + + else: + self.itemFound = None + self.startPos = None + + self.putText(t) + + + def addIdent(self, t): + "Add an identifier." + + o = self.options + + # Make base font bold. + fam, b, i = fonts.ps2tt(o.fontName) + ps = fonts.tt2ps(fam, 1, i) + font = (ps, o.fontSize) + + self.setFillColorAndFont(o.identCol, font) + self.putText(t) + + # Bookmark certain identifiers (class and function names). + if not o.noOutline and not o.multiPage: + item = self.itemFound + if item: + # Add line height to current vert. position. + pos = self.text.getY() + o.fontSize + + nameTag = "p%sy%s" % (self.pageNum, pos) + c = self.canvas + i = self.startPositions.index(self.startPos) + c.bookmarkHorizontalAbsolute(nameTag, pos) + c.addOutlineEntry('%s %s' % (item, t), nameTag, i) + + + def addParam(self, t): + "Add a parameter." + + o = self.options + font = (o.fontName, o.fontSize) + self.setFillColorAndFont(o.paramCol, font) + self.text.putText(t) + + + def addSimpleString(self, t): + "Add a simple string." + + o = self.options + font = (o.fontName, o.fontSize) + self.setFillColorAndFont(o.strngCol, font) + self.putText(t) + + + def addTripleStringBegin(self): + "Memorize begin of a multi-line string." + + # Memorise that we started a multi-line string. + self.multiLineStringStarted = 1 + + + def addTripleStringEnd(self, t): + "Add a multi-line string." + + self.putText(t) + + # Forget about the multi-line string again. + self.multiLineStringStarted = 0 + + + def addComm(self, t): + "Add a comment." + + o = self.options + + # Make base font slanted. + fam, b, i = fonts.ps2tt(o.fontName) + ps = fonts.tt2ps(fam, b, 1) + font = (ps, o.fontSize) + + self.setFillColorAndFont(o.commCol, font) + self.putText(t) + + + def addRest(self, line, eol): + "Add a regular thing." + + o = self.options + + # Nothing else to be done, print line as-is... + if line: + font = (o.fontName, o.fontSize) + self.setFillColorAndFont(o.restCol, font) + + # ... except if the multi-line-string flag is set, then we + # decide to change the current color to that of strings and + # just go on. + if self.multiLineStringStarted: + self.setFillColorAndFont(o.strngCol, font) + + self.putText(line) + + # Print an empty line. + else: + if eol != -1: + self.putText('') + + ### End of API. + + +class EmptyPythonPDFLayouter (PythonPDFLayouter): + """A PDF layout with no decoration and no margins. + + The main frame extends fully to all paper edges. This is + useful for creating PDFs when writing one page per file, + in order to provide pre-rendered, embellished Python + source code to magazine publishers, who can include the + individual files and only need to add their own captures. + """ + + def setMainFrame(self, frame=None): + "Make a frame extending to all paper edges." + + width, height = self.options.realPaperFormat.size + self.frame = height, 0, 0, width + + + def putPageDecoration(self): + "Draw no decoration at all." + + pass + + +### Pretty-printing classes. + +class PDFPrinter: + """Generic PDF Printer class. + + Does not do much, but write a PDF file created from + any ASCII input file. + """ + + outFileExt = '.pdf' + + + def __init__(self, options=None): + "Initialisation." + + self.data = None # Contains the input file. + self.inPath = None # Path of input file. + self.Layouter = PDFLayouter + + if type(options) != type(None): + self.options = options + else: + self.options = Options() + + + ### I/O. + + def readFile(self, path): + "Read the content of a file." + + if path == sys.stdin: + f = path + else: + f = open(path) + + self.inPath = path + + data = f.read() + o = self.options + self.data = re.sub('\t', ' '*o.tabSize, data) + f.close() + + + def formatLine(self, line, eol=0): + "Format one line of Python source code." + + font = ('Courier', 8) + self.layouter.setFillColorAndFont(Color(0, 0, 0), font) + self.layouter.putText(line) + + + def writeData(self, srcCodeLines, inPath, outPath=None): + "Convert Python source code lines into a PDF document." + + # Create a layouter object. + self.layouter = self.Layouter(self.options) + l = self.layouter + + # Loop over all tagged source lines, dissect them into + # Python entities ourself and let the layouter do the + # rendering. + splitCodeLines = string.split(srcCodeLines, '\n') + + ### Must also handle the case of outPath being sys.stdout!! + l.begin(inPath, len(splitCodeLines)) + l.beginDocument() + + for line in splitCodeLines: + l.beginLine() + self.formatLine(line) + l.endLine() + + l.endDocument() + l.end() + + + def writeFile(self, data, inPath=None, outPath=None): + "Write some data into a file." + + if inPath == sys.stdin: + self.outPath = sys.stdout + else: + if not outPath: + path = os.path.splitext(self.inPath)[0] + self.outPath = path + self.outFileExt + + self.writeData(data, inPath, outPath or self.outPath) + + + def process(self, inPath, outPath=None): + "The real 'action point' for working with Pretty-Printers." + + self.readFile(inPath) + self.writeFile(self.data, inPath, outPath) + + +class PythonPDFPrinter (PDFPrinter): + """A class to nicely format tagged Python source code. + + """ + + comm = 'COMMENT' + kw = 'KEYWORD' + strng = 'STRING' + ident = 'IDENT' + param = 'PARAMETER' + + outFileExt = '.pdf' + + + def __init__(self, options=None): + "Initialisation, calling self._didInit() at the end." + + if type(options) != type(None): + self.options = options + else: + self.options = Options() + + self._didInit() + + + def _didInit(self): + "Post-Initialising" + + # Define regular expression patterns. + s = self + comp = re.compile + + s.commPat = comp('(.*?)<' + s.comm + '>(.*?)</' + s.comm + '>(.*)') + s.kwPat = comp('(.*?)<' + s.kw + '>(.*?)</' + s.kw + '>(.*)') + s.identPat = comp('(.*?)<' + s.ident + '>(.*?)</' + s.ident + '>(.*)') + s.paramPat = comp('(.*?)<' + s.param + '>(.*?)</' + s.param + '>(.*)') + s.stPat = comp('(.*?)<' + s.strng + '>(.*?)</' + s.strng + '>(.*)') + s.strng1Pat = comp('(.*?)<' + s.strng + '>(.*)') + s.strng2Pat = comp('(.*?)</' + s.strng + '>(.*)') + s.allPat = comp('(.*)') + + cMatch = s.commPat.match + kMatch = s.kwPat.match + iMatch = s.identPat.match + pMatch = s.paramPat.match + sMatch = s.stPat.match + s1Match = s.strng1Pat.match + s2Match = s.strng2Pat.match + aMatch = s.allPat.match + + self.matchList = ((cMatch, 'Comm'), + (kMatch, 'Kw'), + (iMatch, 'Ident'), + (pMatch, 'Param'), + (sMatch, 'SimpleString'), + (s1Match, 'TripleStringBegin'), + (s2Match, 'TripleStringEnd'), + (aMatch, 'Rest')) + + self.Layouter = PythonPDFLayouter + + # Load fontifier. + self.tagFunc = loadFontifier(self.options) + + + ### + + def formatLine(self, line, eol=0): + "Format one line of Python source code." + + # Values for eol: -1:no-eol, 0:dunno-yet, 1:do-eol. + + for match, meth in self.matchList: + res = match(line) + + if res: + groups = res.groups() + method = getattr(self, '_format%s' % meth) + method(groups, eol) + break + + + def _formatIdent(self, groups, eol): + "Format a Python identifier." + + before, id, after = groups + self.formatLine(before, -1) + self.layouter.addIdent(id) + self.formatLine(after, eol) + + + def _formatParam(self, groups, eol): + "Format a Python parameter." + + before, param, after = groups + self.formatLine(before, -1) + self.layouter.addParam(before) + self.formatLine(after, eol) + + + def _formatSimpleString(self, groups, eol): + "Format a Python one-line string." + + before, s, after = groups + self.formatLine(before, -1) + self.layouter.addSimpleString(s) + self.formatLine(after, eol) + + + def _formatTripleStringBegin(self, groups, eol): + "Format a Python multi-line line string (1)." + + before, after = groups + self.formatLine(before, -1) + self.layouter.addTripleStringBegin() + self.formatLine(after, 1) + + + def _formatTripleStringEnd(self, groups, eol): + "Format a Python multi-line line string (2)." + + before, after = groups + self.layouter.addTripleStringEnd(before) + self.formatLine(after, 1) + + + def _formatKw(self, groups, eol): + "Format a Python keyword." + + before, kw, after = groups + self.formatLine(before, -1) + self.layouter.addKw(kw) + self.formatLine(after, eol) + + + def _formatComm(self, groups, eol): + "Format a Python comment." + + before, comment, after = groups + self.formatLine(before, -1) + self.layouter.addComm(comment) + self.formatLine(after, 1) + + + def _formatRest(self, groups, eol): + "Format a piece of a Python line w/o anything special." + + line = groups[0] + self.layouter.addRest(line, eol) + + + ### + + def writeData(self, srcCodeLines, inPath, outPath=None): + "Convert Python source code lines into a PDF document." + + # Create a layouter object. + self.layouter = self.Layouter(self.options) + l = self.layouter + + # Loop over all tagged source lines, dissect them into + # Python entities ourself and let the layouter do the + # rendering. + splitCodeLines = string.split(srcCodeLines, '\n') + l.begin(inPath, len(splitCodeLines)) + l.beginDocument() + + for line in splitCodeLines: + l.beginLine() + self.formatLine(line) + l.endLine() + + l.endDocument() + l.end() + + + def process(self, inPath, outPath=None): + "The real 'action point' for working with Pretty-Printers." + + self.readFile(inPath) + self.taggedData = self._fontify(self.data) + self.writeFile(self.taggedData, inPath, outPath) + + + ### Fontifying. + + def _fontify(self, pytext): + "" + + formats = { + 'rest' : ('', ''), + 'comment' : ('<%s>' % self.comm, '</%s>' % self.comm), + 'keyword' : ('<%s>' % self.kw, '</%s>' % self.kw), + 'parameter' : ('<%s>' % self.param, '</%s>' % self.param), + 'identifier' : ('<%s>' % self.ident, '</%s>' % self.ident), + 'string' : ('<%s>' % self.strng, '</%s>' % self.strng) } + + # Parse. + taglist = self.tagFunc(pytext) + + # Prepend special 'rest' tag. + taglist[:0] = [('rest', 0, len(pytext), None)] + + # Prepare splitting. + splits = [] + self._addSplits(splits, pytext, formats, taglist) + + # Do splitting & inserting. + splits.sort() + l = [] + li = 0 + + for ri, dummy, insert in splits: + if ri > li: + l.append(pytext[li:ri]) + + l.append(insert) + li = ri + + if li < len(pytext): + l.append(pytext[li:]) + + return string.join(l, '') + + + def _addSplits(self, splits, text, formats, taglist): + "" + + # Helper for fontify(). + for id, left, right, sublist in taglist: + + try: + pre, post = formats[id] + except KeyError: + # msg = 'Warning: no format ' + # msg = msg + 'for %s specified\n'%repr(id) + # sys.stderr.write(msg) + pre, post = '', '' + + if type(pre) != type(''): + pre = pre(text[left:right]) + + if type(post) != type(''): + post = post(text[left:right]) + + # len(splits) is a dummy used to make sorting stable. + splits.append((left, len(splits), pre)) + + if sublist: + self._addSplits(splits, text, formats, sublist) + + splits.append((right, len(splits), post)) + + +### Main + +def main(cmdline): + "Process command line as if it were sys.argv" + + # Create default options and initialize with argv + # from the command line. + options = Options() + options.updateWithContentsOfArgv(cmdline[1:]) + + # Print help message if desired, then exit. + if options.h or options.help: + print __doc__ + sys.exit() + + # Apply modest consistency checks and exit if needed. + cmdStr = string.join(cmdline, ' ') + find = string.find + if find(cmdStr, 'paperSize') >= 0 and find(cmdStr, 'paperFormat') >= 0: + details = "You can specify either paperSize or paperFormat, " + details = detail + "but not both!" + raise 'ValueError', details + + # Create PDF converter and pass options to it. + if options.input: + input = string.lower(options.input) + + if input == 'python': + P = PythonPDFPrinter + elif input == 'ascii': + P = PDFPrinter + else: + details = "Input file type must be 'python' or 'ascii'." + raise 'ValueError', details + + else: + P = PythonPDFPrinter + + p = P(options) + + # Display options if needed. + if options.v or options.verbose: + pass # p.options.display() + + # Start working. + verbose = options.v or options.verbose + + if options.stdout: + if len(options.files) > 1 and verbose: + print "Warning: will only convert first file on command line." + f = options.files[0] + p.process(f, sys.stdout) + else: + if verbose: + print 'py2pdf: working on:' + + for f in options.files: + try: + if verbose: + print ' %s' % f + if f != '-': + p.process(f) + else: + p.process(sys.stdin, sys.stdout) + except IOError: + if verbose: + print '(IOError!)', + + if verbose: + print + print 'Done.' + + +def process(*args, **kwargs): + "Provides a way of using py2pdf from within other Python scripts." + + noValOpts = 'h v verbose landscape stdout lineNum marcs help multiPage noOutline' + noValOpts = string.split(noValOpts, ' ') + + s = 'py2pdf.py' + for k, v in kwargs.items(): + if len(k) == 1: + s = s + ' -%s' % k + if k not in noValOpts: + s = s + ' %s' % v + elif len(k) > 1: + s = s + ' --%s' % k + if k not in noValOpts: + s = s + '=%s' % v + s = s + ' ' + string.join(args, ' ') + s = string.split(s, ' ') + + main(s) + + +### + +if __name__=='__main__': #NORUNTESTS + main(sys.argv) \ No newline at end of file diff --git a/bin/reportlab/tools/py2pdf/vertpython.jpg b/bin/reportlab/tools/py2pdf/vertpython.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8655230bf43d2a8532cefb9f3c44e222ad066447 GIT binary patch literal 22872 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Ð?;C+>QEZ+hrwG>qzn^DlsX)<Ac(y(E(epL9GQ9 z#YjWWmr0_#WT}Yc&LpXoBVm%?F93^0HdW73|LtaWry}cI5zeP()D@8c06m%Jzhc}U zs>ff<9RE+uqu2e-0(5K0Xn*`fk69J2tGKmgW`1G5wmAo45Z^B9(G^#zNDU&jc3?sR zd7CEQnY|6lmA2AAuzR_sWOhhw7;z!Omv*WRPJ*zqilDW<o;zzcANV*Q9jay}6pXaK z8*g5SW4^he6_AjM%+iSrZU8W5Rc|xOoZ?RAr_kX;9)%A2&2Yl?d-c%8J&?>;z!v?W z79@S+Iizop|2|uv$T)CLgf?>Sa;uboImf}ImF($rwVsZ48=v5{Xw_c;3$VC#I+Rg! z+bQlB;A7dB!ZmxnC$=$O4%P$74<BM%{r2&7<{5oTJZ6O!UvFXQvmv%kX^bW9hqDN$ zisF@!7Pc8gmY!GmdR$0;AWI6H#J?^fe?qSR=I;ONh2)QM>0xHd%+xC<g}xAux#Re% zb6_Iyp39v_UWZKBw-2kyHKeNXe9S6AT=)@YLC>zt$^jQJu_0DntI(pCG|q(qCIKFx zsQ?}9$8BFEzY4!0Y;#aidXF%Zaen<#wgzq+ANqVhTccLTOlO2|IWd?NJc+||%f}s7 zeh)**IW;cW@rS}D+#Nc&g+96S-o(MVe1h8XNJbF;(?vq$IoV9%D#%f>wCQk_qE8fQ za$WN}c2F#eI2&c@i?+{T5*tWsyx)0%-^H(y){fjo$0WLTLCKSb)wv2((z}U8^<eP< zxJ~FexnlSyiuh;$S0wRwTixHA9&TFEg)`gx#=8UTgFP^@V5<5z7uP;H?q7fuaQ7+o zFTg!kEOAVv)6EwWBZl!}1ug*yAQ-18m(R7xY3cIj4>`)C7q1=y7RlSh@9KUozNh7- zQ6&A!)+9v{1MRL;q~=U!-L&P`$dT48!P}Lp6l!+o)g4rXignXI?x&gK0Q@Z2Nj8vm zLv1S~4z0Pa*0cwd*3P(*`vmLv?$hf+2ynNYZ{|14tsl+^zPp@MN(YB9Ji))^-r0)Z z_hckiyeIb6Y}_>f470LRM3N`lp6;$$sfv~;v;24L|8pt)&G7r@8Q|80emWgpysF;L zk~A*@#dXutQ1I=PvVA#UrCA%p1YW2IX1;w6;R8ea$i=k!4R-dGS198MD60qGTziUk zz)zIoCYHJ7y==mrI18O{N2Q7x(Gz4?dTc}Z{ZkjmY8tmCVWep+gO)c_Lt=ZhMXr{O z{kW00heSO;B*dbkx0Z%UKhPeLm}g%uzA;u9!qjPtC+tEU<0@{|k}kcm9$^>R&5s)N zd8@)Wh}boI&{ETo=0XSWVHSlj%e6ftJkVy#xZDZpcJ6&Xb`%N2*FW|@6*nm0F_t`m zWfj|WU7q#hh)HgZ3`4`pi-(`cvD&{j;*)=#(RGVnuaEOEIa9AFLr-@gw^(AH$w4gV z3&wP*-V?UJG1kVHY~BpdRL<93?|wbsV48OCe)_jq23^k1^>YqnYGsDArNJ%OU&$1; zfm!irxYmd<rH`ZvU)hv4ev_Tia&;cfdrN~v7B6z?9Va$i+W%BnF1Go#!g;ydNdEt* zJkswN7GwMaPGw7i%$ewE4Jp?=UPbrieV{@NCBtd@{-%os7pJ~j4Bh_n$AcR0B~P6$ z0WV$TWPBOW!C>T^SY_2Tt5GRKR||$MQLpl+S#$~q=D}<TxFj;U7G`<2%y?IWr2c0t zhbktK`AR#PdGUDy$~X0iCtp~JcL>%O?;-DwM4hL}#5gfZcUC^`r3A)1Mcr|cINw0} z*Z-mp|6dzT^n0>^+ClM1lDZuuN>__1Dq+_!TYcrabWL{15VHvY+C5yz|6ErqUOl=! zeu$NV8>yo#(U_Bhc$YC=6><5@Rl~s%6!^BsHB1wyJ!^RWSuTMfHT&+@$!8(C1aRPf yXOOEb64vBNBHB0q0&pmv2{u;b=WS`o#cjI@8fF#iE3=lQ{2M>=ham84;=cflPq#<_ literal 0 HcmV?d00001 diff --git a/bin/reportlab/tools/pythonpoint/README b/bin/reportlab/tools/pythonpoint/README new file mode 100644 index 00000000000..f6d8808bdab --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/README @@ -0,0 +1,29 @@ +PythonPoint is a utility for generating PDF slides from +a simple XML format - "PythonPoint Markup Language". + +This is early days. It lets you produce quite sophisticated +output, but the DTD will undoubtedly evolve and change. +However, I want people to be able to use it this summer so am +releasing now. + +It is part of the ReportLab distribution; if you have managed +to run the ReportLab tests, and have installed the Python +Imaging Library, usage should be straightforward. PIL is not +required to make slides, but the demo will have a few blanks if +you omit it. + +To use, cd to the pythonpoint directory and execute: + pythonpoint.py pythonpoint.xml +This will create pythonpoint.pdf, which is your manual. + +You can also try 'monterey.xml', which is a talk I gave +at the O'Reilly Open Source conference in Monterey +in summer 1999. + +We have issues to resolve over module load paths; +the easiest solution for now is to put the PythonPoint +directory on your path; work in a new directory; +and explicitly include the paths to any custom shapes, +and style sheets you use. + +- Andy Robinson, 6 April 2000 \ No newline at end of file diff --git a/bin/reportlab/tools/pythonpoint/__init__.py b/bin/reportlab/tools/pythonpoint/__init__.py new file mode 100644 index 00000000000..c4b994f848b --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/__init__.py @@ -0,0 +1,3 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/pythonpoint/__init__.py diff --git a/bin/reportlab/tools/pythonpoint/customshapes.py b/bin/reportlab/tools/pythonpoint/customshapes.py new file mode 100644 index 00000000000..779a236eec6 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/customshapes.py @@ -0,0 +1,298 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/pythonpoint/customshapes.py +__version__=''' $Id: customshapes.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' + +# xml parser stuff for PythonPoint +# PythonPoint Markup Language! + +__doc__=""" +This demonstrates a custom shape for use with the <customshape> tag. +The shape must fulfil a very simple interface, which may change in +future. + +The XML tag currently has this form: + <customshape + module="customshapes.py" + class = "MyShape" + initargs="(100,200,3)" + /> + +PythonPoint will look in the given module for the given class, +evaluate the arguments string and pass it to the constructor. +Then, it will call + + object.drawOn(canvas) + +Thus your object must be fully defined by the constructor. +For this one, we pass three argumenyts: x, y and scale. +This does a five-tile jigsaw over which words can be overlaid; +based on work done for a customer's presentation. +""" + + +import reportlab.pdfgen.canvas +from reportlab.lib import colors +from reportlab.lib.corp import RL_CorpLogo +from reportlab.graphics.shapes import Drawing + +## custom shape for use with PythonPoint. + +class Jigsaw: + """This draws a jigsaw patterm. By default it is centred on 0,0 + and has dimensions of 200 x 140; use the x/y/scale attributes + to move it around.""" + #Using my usual bulldozer coding style - I am sure a mathematician could + #derive an elegant way to draw this, but I just took a ruler, guessed at + #the control points, and reflected a few lists at the interactive prompt. + + def __init__(self, x, y, scale=1): + self.width = 200 + self.height = 140 + self.x = x + self.y = y + self.scale = scale + + + def drawOn(self, canvas): + canvas.saveState() + + canvas.setFont('Helvetica-Bold',24) + canvas.drawString(600, 100, 'A Custom Shape') + + canvas.translate(self.x, self.y) + canvas.scale(self.scale, self.scale) + self.drawBounds(canvas) + + self.drawCentre(canvas) + self.drawTopLeft(canvas) + self.drawBottomLeft(canvas) + self.drawBottomRight(canvas) + self.drawTopRight(canvas) + + canvas.restoreState() + + + def curveThrough(self, path, pointlist): + """Helper to curve through set of control points.""" + assert len(pointlist) % 3 == 1, "No. of points must be 3n+1 for integer n" + (x,y) = pointlist[0] + path.moveTo(x, y) + idx = 1 + while idx < len(pointlist)-2: + p1, p2, p3 = pointlist[idx:idx+3] + path.curveTo(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]) + idx = idx + 3 + + + def drawShape(self, canvas, controls, color): + """Utlity to draw a closed shape through a list of control points; + extends the previous proc""" + canvas.setFillColor(color) + p = canvas.beginPath() + self.curveThrough(p, controls) + p.close() + canvas.drawPath(p, stroke=1, fill=1) + + + def drawBounds(self, canvas): + """Guidelines to help me draw - not needed in production""" + canvas.setStrokeColor(colors.red) + canvas.rect(-100,-70,200,140) + canvas.line(-100,0,100,0) + canvas.line(0,70,0,-70) + canvas.setStrokeColor(colors.black) + + + def drawCentre(self, canvas): + controls = [ (0,50), #top + + #top right edge - duplicated for that corner piece + (5,50),(10,45),(10,40), + (10,35),(15,30),(20,30), + (25,30),(30,25),(30,20), + (30,15),(35,10),(40,10), + (45,10),(50,5),(50,0), + + #bottom right edge + (50, -5), (45,-10), (40,-10), + (35,-10), (30,-15), (30, -20), + (30,-25), (25,-30), (20,-30), + (15,-30), (10,-35), (10,-40), + (10,-45),(5,-50),(0,-50), + + #bottom left + (-5,-50),(-10,-45),(-10,-40), + (-10,-35),(-15,-30),(-20,-30), + (-25,-30),(-30,-25),(-30,-20), + (-30,-15),(-35,-10),(-40,-10), + (-45,-10),(-50,-5),(-50,0), + + #top left + (-50,5),(-45,10),(-40,10), + (-35,10),(-30,15),(-30,20), + (-30,25),(-25,30),(-20,30), + (-15,30),(-10,35),(-10,40), + (-10,45),(-5,50),(0,50) + + ] + + self.drawShape(canvas, controls, colors.yellow) + + + def drawTopLeft(self, canvas): + controls = [(-100,70), + (-100,69),(-100,1),(-100,0), + (-99,0),(-91,0),(-90,0), + + #jigsaw interlock - 4 sections + (-90,5),(-92,5),(-92,10), + (-92,15), (-85,15), (-80,15), + (-75,15),(-68,15),(-68,10), + (-68,5),(-70,5),(-70,0), + (-69,0),(-51,0),(-50,0), + + #five distinct curves + (-50,5),(-45,10),(-40,10), + (-35,10),(-30,15),(-30,20), + (-30,25),(-25,30),(-20,30), + (-15,30),(-10,35),(-10,40), + (-10,45),(-5,50),(0,50), + + (0,51),(0,69),(0,70), + (-1,70),(-99,70),(-100,70) + ] + self.drawShape(canvas, controls, colors.teal) + + + def drawBottomLeft(self, canvas): + + controls = [(-100,-70), + (-99,-70),(-1,-70),(0,-70), + (0,-69),(0,-51),(0,-50), + + #wavyline + (-5,-50),(-10,-45),(-10,-40), + (-10,-35),(-15,-30),(-20,-30), + (-25,-30),(-30,-25),(-30,-20), + (-30,-15),(-35,-10),(-40,-10), + (-45,-10),(-50,-5),(-50,0), + + #jigsaw interlock - 4 sections + + (-51, 0), (-69, 0), (-70, 0), + (-70, 5), (-68, 5), (-68, 10), + (-68, 15), (-75, 15), (-80, 15), + (-85, 15), (-92, 15), (-92, 10), + (-92, 5), (-90, 5), (-90, 0), + + (-91,0),(-99,0),(-100,0) + + ] + self.drawShape(canvas, controls, colors.green) + + + def drawBottomRight(self, canvas): + + controls = [ (100,-70), + (100,-69),(100,-1),(100,0), + (99,0),(91,0),(90,0), + + #jigsaw interlock - 4 sections + (90, -5), (92, -5), (92, -10), + (92, -15), (85, -15), (80, -15), + (75, -15), (68, -15), (68, -10), + (68, -5), (70, -5), (70, 0), + (69, 0), (51, 0), (50, 0), + + #wavyline + (50, -5), (45,-10), (40,-10), + (35,-10), (30,-15), (30, -20), + (30,-25), (25,-30), (20,-30), + (15,-30), (10,-35), (10,-40), + (10,-45),(5,-50),(0,-50), + + (0,-51), (0,-69), (0,-70), + (1,-70),(99,-70),(100,-70) + + ] + self.drawShape(canvas, controls, colors.navy) + + + def drawBottomLeft(self, canvas): + + controls = [(-100,-70), + (-99,-70),(-1,-70),(0,-70), + (0,-69),(0,-51),(0,-50), + + #wavyline + (-5,-50),(-10,-45),(-10,-40), + (-10,-35),(-15,-30),(-20,-30), + (-25,-30),(-30,-25),(-30,-20), + (-30,-15),(-35,-10),(-40,-10), + (-45,-10),(-50,-5),(-50,0), + + #jigsaw interlock - 4 sections + + (-51, 0), (-69, 0), (-70, 0), + (-70, 5), (-68, 5), (-68, 10), + (-68, 15), (-75, 15), (-80, 15), + (-85, 15), (-92, 15), (-92, 10), + (-92, 5), (-90, 5), (-90, 0), + + (-91,0),(-99,0),(-100,0) + + ] + self.drawShape(canvas, controls, colors.green) + + + def drawTopRight(self, canvas): + controls = [(100, 70), + (99, 70), (1, 70), (0, 70), + (0, 69), (0, 51), (0, 50), + (5, 50), (10, 45), (10, 40), + (10, 35), (15, 30), (20, 30), + (25, 30), (30, 25), (30, 20), + (30, 15), (35, 10), (40, 10), + (45, 10), (50, 5), (50, 0), + (51, 0), (69, 0), (70, 0), + (70, -5), (68, -5), (68, -10), + (68, -15), (75, -15), (80, -15), + (85, -15), (92, -15), (92, -10), + (92, -5), (90, -5), (90, 0), + (91, 0), (99, 0), (100, 0) + ] + + self.drawShape(canvas, controls, colors.magenta) + + +class Logo: + """This draws a ReportLab Logo.""" + + def __init__(self, x, y, width, height): + logo = RL_CorpLogo() + logo.x = x + logo.y = y + logo.width = width + logo.height = height + self.logo = logo + + def drawOn(self, canvas): + logo = self.logo + x, y = logo.x, logo.y + w, h = logo.width, logo.height + D = Drawing(w, h) + D.add(logo) + D.drawOn(canvas, 0, 0) + + +def run(): + c = reportlab.pdfgen.canvas.Canvas('customshape.pdf') + + J = Jigsaw(300, 540, 2) + J.drawOn(c) + c.save() + + +if __name__ == '__main__': + run() \ No newline at end of file diff --git a/bin/reportlab/tools/pythonpoint/demos/LeERC___.AFM b/bin/reportlab/tools/pythonpoint/demos/LeERC___.AFM new file mode 100644 index 00000000000..fe491e473a2 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/demos/LeERC___.AFM @@ -0,0 +1,93 @@ +StartFontMetrics 2.0 +Comment Generated by RoboFog 08-06-2001 4:26:40 PM +FontName LettErrorRobot-Chrome +FullName LettErrorRobot-Chrome +FamilyName LettErrorRobot +Weight Medium +Notice (C) 1998-2001 LettError, Just van Rossum, Erik van Blokland, http://www.letterror.com/ +ItalicAngle 0 +IsFixedPitch false +UnderlinePosition -133 +UnderlineThickness 20 +Version 001.000 +EncodingScheme AdobeStandardEncoding +FontBBox -53 -252 1047 752 +CapHeight 647 +XHeight 548 +Descender -252 +Ascender 747 +StartCharMetrics 68 +C 32 ; WX 300 ; N space ; B 0 0 0 0 ; +C 33 ; WX 300 ; N exclam ; B 48 -52 253 652 ; +C 38 ; WX 700 ; N ampersand ; B 48 -47 653 647 ; +C 42 ; WX 700 ; N asterisk ; B 48 148 653 752 ; +C 48 ; WX 700 ; N zero ; B 53 -47 648 548 ; +C 49 ; WX 500 ; N one ; B 48 -47 453 553 ; +C 50 ; WX 600 ; N two ; B 48 -47 553 548 ; +C 51 ; WX 600 ; N three ; B 48 -147 553 548 ; +C 52 ; WX 700 ; N four ; B 48 -152 653 553 ; +C 53 ; WX 600 ; N five ; B 48 -147 553 548 ; +C 54 ; WX 600 ; N six ; B 53 -47 553 647 ; +C 55 ; WX 600 ; N seven ; B 48 -152 548 548 ; +C 56 ; WX 600 ; N eight ; B 48 -47 553 647 ; +C 57 ; WX 600 ; N nine ; B 48 -147 548 548 ; +C 63 ; WX 500 ; N question ; B 48 -52 448 647 ; +C 64 ; WX 800 ; N at ; B 53 -47 748 647 ; +C 65 ; WX 700 ; N A ; B 53 -52 648 652 ; +C 66 ; WX 600 ; N B ; B 53 -47 553 647 ; +C 67 ; WX 600 ; N C ; B 53 -47 553 647 ; +C 68 ; WX 700 ; N D ; B 53 -47 648 647 ; +C 69 ; WX 600 ; N E ; B 53 -47 553 647 ; +C 70 ; WX 600 ; N F ; B 53 -52 553 647 ; +C 71 ; WX 700 ; N G ; B 53 -47 653 647 ; +C 72 ; WX 700 ; N H ; B 53 -52 648 652 ; +C 73 ; WX 300 ; N I ; B 53 -52 248 652 ; +C 74 ; WX 300 ; N J ; B -53 -252 248 652 ; +C 75 ; WX 700 ; N K ; B 53 -52 653 652 ; +C 76 ; WX 600 ; N L ; B 53 -47 553 652 ; +C 77 ; WX 900 ; N M ; B 53 -52 848 652 ; +C 78 ; WX 700 ; N N ; B 53 -52 648 652 ; +C 79 ; WX 700 ; N O ; B 53 -47 648 647 ; +C 80 ; WX 600 ; N P ; B 53 -52 548 647 ; +C 81 ; WX 700 ; N Q ; B 53 -252 653 647 ; +C 82 ; WX 600 ; N R ; B 53 -52 653 647 ; +C 83 ; WX 600 ; N S ; B 48 -47 553 647 ; +C 84 ; WX 700 ; N T ; B 48 -52 653 647 ; +C 85 ; WX 700 ; N U ; B 53 -47 648 652 ; +C 86 ; WX 700 ; N V ; B 53 -52 648 652 ; +C 87 ; WX 1100 ; N W ; B 53 -52 1047 652 ; +C 88 ; WX 700 ; N X ; B 48 -52 653 652 ; +C 89 ; WX 700 ; N Y ; B 53 -52 648 652 ; +C 90 ; WX 700 ; N Z ; B 48 -47 653 647 ; +C 97 ; WX 600 ; N a ; B 48 -47 548 548 ; +C 98 ; WX 600 ; N b ; B 53 -47 548 752 ; +C 99 ; WX 600 ; N c ; B 53 -47 553 548 ; +C 100 ; WX 600 ; N d ; B 53 -47 548 752 ; +C 101 ; WX 600 ; N e ; B 53 -47 553 548 ; +C 102 ; WX 400 ; N f ; B -53 -52 453 747 ; +C 103 ; WX 600 ; N g ; B 48 -247 548 548 ; +C 104 ; WX 600 ; N h ; B 53 -52 548 752 ; +C 105 ; WX 300 ; N i ; B 48 -52 253 752 ; +C 106 ; WX 300 ; N j ; B -53 -252 253 752 ; +C 107 ; WX 600 ; N k ; B 53 -52 553 752 ; +C 108 ; WX 300 ; N l ; B 53 -52 248 752 ; +C 109 ; WX 900 ; N m ; B 53 -52 848 548 ; +C 110 ; WX 600 ; N n ; B 53 -52 548 548 ; +C 111 ; WX 600 ; N o ; B 53 -47 548 548 ; +C 112 ; WX 600 ; N p ; B 53 -252 548 548 ; +C 113 ; WX 600 ; N q ; B 53 -252 548 548 ; +C 114 ; WX 400 ; N r ; B 53 -52 453 553 ; +C 115 ; WX 600 ; N s ; B 48 -47 553 548 ; +C 116 ; WX 400 ; N t ; B -53 -47 453 652 ; +C 117 ; WX 600 ; N u ; B 53 -47 548 553 ; +C 118 ; WX 600 ; N v ; B 53 -52 548 553 ; +C 119 ; WX 900 ; N w ; B 53 -52 848 553 ; +C 120 ; WX 700 ; N x ; B 48 -52 653 553 ; +C 121 ; WX 600 ; N y ; B 48 -252 548 553 ; +C 122 ; WX 500 ; N z ; B -53 -47 553 548 ; +EndCharMetrics +StartKernData +StartKernPairs 0 +EndKernPairs +EndKernData +EndFontMetrics diff --git a/bin/reportlab/tools/pythonpoint/demos/LeERC___.PFB b/bin/reportlab/tools/pythonpoint/demos/LeERC___.PFB new file mode 100644 index 0000000000000000000000000000000000000000..c1cf7ec189aa781e12da6269e10b314cb8cb99cd GIT binary patch literal 48590 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 z�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 literal 0 HcmV?d00001 diff --git a/bin/reportlab/tools/pythonpoint/demos/examples.py b/bin/reportlab/tools/pythonpoint/demos/examples.py new file mode 100644 index 00000000000..14aee2336fb --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/demos/examples.py @@ -0,0 +1,841 @@ +import string + +testannotations=""" +def annotations(canvas): + from reportlab.lib.units import inch + canvas.drawString(inch, 2.5*inch, + "setAuthor, setTitle, setSubject have no visible effect") + canvas.drawString(inch, inch, "But if you are viewing this document dynamically") + canvas.drawString(inch, 0.5*inch, "please look at File/Document Info") + canvas.setAuthor("the ReportLab Team") + canvas.setTitle("ReportLab PDF Generation User Guide") + canvas.setSubject("How to Generate PDF files using the ReportLab modules") +""" + +# magic function making module + +test1 = """ +def f(a,b): + print "it worked", a, b + return a+b +""" + +test2 = """ +def g(n): + if n==0: return 1 + else: return n*g(n-1) + """ + +testhello = """ +def hello(c): + from reportlab.lib.units import inch + # move the origin up and to the left + c.translate(inch,inch) + # define a large font + c.setFont("Helvetica", 14) + # choose some colors + c.setStrokeColorRGB(0.2,0.5,0.3) + c.setFillColorRGB(1,0,1) + # draw some lines + c.line(0,0,0,1.7*inch) + c.line(0,0,1*inch,0) + # draw a rectangle + c.rect(0.2*inch,0.2*inch,1*inch,1.5*inch, fill=1) + # make text go straight up + c.rotate(90) + # change color + c.setFillColorRGB(0,0,0.77) + # say hello (note after rotate the y coord needs to be negative!) + c.drawString(0.3*inch, -inch, "Hello World") +""" + +testcoords = """ +def coords(canvas): + from reportlab.lib.units import inch + from reportlab.lib.colors import pink, black, red, blue, green + c = canvas + c.setStrokeColor(pink) + c.grid([inch, 2*inch, 3*inch, 4*inch], [0.5*inch, inch, 1.5*inch, 2*inch, 2.5*inch]) + c.setStrokeColor(black) + c.setFont("Times-Roman", 20) + c.drawString(0,0, "(0,0) the Origin") + c.drawString(2.5*inch, inch, "(2.5,1) in inches") + c.drawString(4*inch, 2.5*inch, "(4, 2.5)") + c.setFillColor(red) + c.rect(0,2*inch,0.2*inch,0.3*inch, fill=1) + c.setFillColor(green) + c.circle(4.5*inch, 0.4*inch, 0.2*inch, fill=1) +""" + +testtranslate = """ +def translate(canvas): + from reportlab.lib.units import cm + canvas.translate(2.3*cm, 0.3*cm) + coords(canvas) + """ + +testscale = """ +def scale(canvas): + canvas.scale(0.75, 0.5) + coords(canvas) +""" + +testscaletranslate = """ +def scaletranslate(canvas): + from reportlab.lib.units import inch + canvas.setFont("Courier-BoldOblique", 12) + # save the state + canvas.saveState() + # scale then translate + canvas.scale(0.3, 0.5) + canvas.translate(2.4*inch, 1.5*inch) + canvas.drawString(0, 2.7*inch, "Scale then translate") + coords(canvas) + # forget the scale and translate... + canvas.restoreState() + # translate then scale + canvas.translate(2.4*inch, 1.5*inch) + canvas.scale(0.3, 0.5) + canvas.drawString(0, 2.7*inch, "Translate then scale") + coords(canvas) +""" + +testmirror = """ +def mirror(canvas): + from reportlab.lib.units import inch + canvas.translate(5.5*inch, 0) + canvas.scale(-1.0, 1.0) + coords(canvas) +""" + +testcolors = """ +def colors(canvas): + from reportlab.lib import colors + from reportlab.lib.units import inch + black = colors.black + y = x = 0; dy=inch*3/4.0; dx=inch*5.5/5; w=h=dy/2; rdx=(dx-w)/2 + rdy=h/5.0; texty=h+2*rdy + canvas.setFont("Helvetica",10) + for [namedcolor, name] in ( + [colors.lavenderblush, "lavenderblush"], + [colors.lawngreen, "lawngreen"], + [colors.lemonchiffon, "lemonchiffon"], + [colors.lightblue, "lightblue"], + [colors.lightcoral, "lightcoral"]): + canvas.setFillColor(namedcolor) + canvas.rect(x+rdx, y+rdy, w, h, fill=1) + canvas.setFillColor(black) + canvas.drawCentredString(x+dx/2, y+texty, name) + x = x+dx + y = y + dy; x = 0 + for rgb in [(1,0,0), (0,1,0), (0,0,1), (0.5,0.3,0.1), (0.4,0.5,0.3)]: + r,g,b = rgb + canvas.setFillColorRGB(r,g,b) + canvas.rect(x+rdx, y+rdy, w, h, fill=1) + canvas.setFillColor(black) + canvas.drawCentredString(x+dx/2, y+texty, "r%s g%s b%s"%rgb) + x = x+dx + y = y + dy; x = 0 + for cmyk in [(1,0,0,0), (0,1,0,0), (0,0,1,0), (0,0,0,1), (0,0,0,0)]: + c,m,y1,k = cmyk + canvas.setFillColorCMYK(c,m,y1,k) + canvas.rect(x+rdx, y+rdy, w, h, fill=1) + canvas.setFillColor(black) + canvas.drawCentredString(x+dx/2, y+texty, "c%s m%s y%s k%s"%cmyk) + x = x+dx + y = y + dy; x = 0 + for gray in (0.0, 0.25, 0.50, 0.75, 1.0): + canvas.setFillGray(gray) + canvas.rect(x+rdx, y+rdy, w, h, fill=1) + canvas.setFillColor(black) + canvas.drawCentredString(x+dx/2, y+texty, "gray: %s"%gray) + x = x+dx +""" + +testspumoni = """ +def spumoni(canvas): + from reportlab.lib.units import inch + from reportlab.lib.colors import pink, green, brown, white + x = 0; dx = 0.4*inch + for i in range(4): + for color in (pink, green, brown): + canvas.setFillColor(color) + canvas.rect(x,0,dx,3*inch,stroke=0,fill=1) + x = x+dx + canvas.setFillColor(white) + canvas.setStrokeColor(white) + canvas.setFont("Helvetica-Bold", 85) + canvas.drawCentredString(2.75*inch, 1.3*inch, "SPUMONI") +""" + +testspumoni2 = """ +def spumoni2(canvas): + from reportlab.lib.units import inch + from reportlab.lib.colors import pink, green, brown, white, black + # draw the previous drawing + spumoni(canvas) + # now put an ice cream cone on top of it: + # first draw a triangle (ice cream cone) + p = canvas.beginPath() + xcenter = 2.75*inch + radius = 0.45*inch + p.moveTo(xcenter-radius, 1.5*inch) + p.lineTo(xcenter+radius, 1.5*inch) + p.lineTo(xcenter, 0) + canvas.setFillColor(brown) + canvas.setStrokeColor(black) + canvas.drawPath(p, fill=1) + # draw some circles (scoops) + y = 1.5*inch + for color in (pink, green, brown): + canvas.setFillColor(color) + canvas.circle(xcenter, y, radius, fill=1) + y = y+radius +""" + +testbezier = """ +def bezier(canvas): + from reportlab.lib.colors import yellow, green, red, black + from reportlab.lib.units import inch + i = inch + d = i/4 + # define the bezier curve control points + x1,y1, x2,y2, x3,y3, x4,y4 = d,1.5*i, 1.5*i,d, 3*i,d, 5.5*i-d,3*i-d + # draw a figure enclosing the control points + canvas.setFillColor(yellow) + p = canvas.beginPath() + p.moveTo(x1,y1) + for (x,y) in [(x2,y2), (x3,y3), (x4,y4)]: + p.lineTo(x,y) + canvas.drawPath(p, fill=1, stroke=0) + # draw the tangent lines + canvas.setLineWidth(inch*0.1) + canvas.setStrokeColor(green) + canvas.line(x1,y1,x2,y2) + canvas.setStrokeColor(red) + canvas.line(x3,y3,x4,y4) + # finally draw the curve + canvas.setStrokeColor(black) + canvas.bezier(x1,y1, x2,y2, x3,y3, x4,y4) +""" + +testbezier2 = """ +def bezier2(canvas): + from reportlab.lib.colors import yellow, green, red, black + from reportlab.lib.units import inch + # make a sequence of control points + xd,yd = 5.5*inch/2, 3*inch/2 + xc,yc = xd,yd + dxdy = [(0,0.33), (0.33,0.33), (0.75,1), (0.875,0.875), + (0.875,0.875), (1,0.75), (0.33,0.33), (0.33,0)] + pointlist = [] + for xoffset in (1,-1): + yoffset = xoffset + for (dx,dy) in dxdy: + px = xc + xd*xoffset*dx + py = yc + yd*yoffset*dy + pointlist.append((px,py)) + yoffset = -xoffset + for (dy,dx) in dxdy: + px = xc + xd*xoffset*dx + py = yc + yd*yoffset*dy + pointlist.append((px,py)) + # draw tangent lines and curves + canvas.setLineWidth(inch*0.1) + while pointlist: + [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] = pointlist[:4] + del pointlist[:4] + canvas.setLineWidth(inch*0.1) + canvas.setStrokeColor(green) + canvas.line(x1,y1,x2,y2) + canvas.setStrokeColor(red) + canvas.line(x3,y3,x4,y4) + # finally draw the curve + canvas.setStrokeColor(black) + canvas.bezier(x1,y1, x2,y2, x3,y3, x4,y4) +""" + +testpencil = """ +def pencil(canvas, text="No.2"): + from reportlab.lib.colors import yellow, red, black,white + from reportlab.lib.units import inch + u = inch/10.0 + canvas.setStrokeColor(black) + canvas.setLineWidth(4) + # draw erasor + canvas.setFillColor(red) + canvas.circle(30*u, 5*u, 5*u, stroke=1, fill=1) + # draw all else but the tip (mainly rectangles with different fills) + canvas.setFillColor(yellow) + canvas.rect(10*u,0,20*u,10*u, stroke=1, fill=1) + canvas.setFillColor(black) + canvas.rect(23*u,0,8*u,10*u,fill=1) + canvas.roundRect(14*u, 3.5*u, 8*u, 3*u, 1.5*u, stroke=1, fill=1) + canvas.setFillColor(white) + canvas.rect(25*u,u,1.2*u,8*u, fill=1,stroke=0) + canvas.rect(27.5*u,u,1.2*u,8*u, fill=1, stroke=0) + canvas.setFont("Times-Roman", 3*u) + canvas.drawCentredString(18*u, 4*u, text) + # now draw the tip + penciltip(canvas,debug=0) + # draw broken lines across the body. + canvas.setDash([10,5,16,10],0) + canvas.line(11*u,2.5*u,22*u,2.5*u) + canvas.line(22*u,7.5*u,12*u,7.5*u) + """ + +testpenciltip = """ +def penciltip(canvas, debug=1): + from reportlab.lib.colors import tan, black, green + from reportlab.lib.units import inch + u = inch/10.0 + canvas.setLineWidth(4) + if debug: + canvas.scale(2.8,2.8) # make it big + canvas.setLineWidth(1) # small lines + canvas.setStrokeColor(black) + canvas.setFillColor(tan) + p = canvas.beginPath() + p.moveTo(10*u,0) + p.lineTo(0,5*u) + p.lineTo(10*u,10*u) + p.curveTo(11.5*u,10*u, 11.5*u,7.5*u, 10*u,7.5*u) + p.curveTo(12*u,7.5*u, 11*u,2.5*u, 9.7*u,2.5*u) + p.curveTo(10.5*u,2.5*u, 11*u,0, 10*u,0) + canvas.drawPath(p, stroke=1, fill=1) + canvas.setFillColor(black) + p = canvas.beginPath() + p.moveTo(0,5*u) + p.lineTo(4*u,3*u) + p.lineTo(5*u,4.5*u) + p.lineTo(3*u,6.5*u) + canvas.drawPath(p, stroke=1, fill=1) + if debug: + canvas.setStrokeColor(green) # put in a frame of reference + canvas.grid([0,5*u,10*u,15*u], [0,5*u,10*u]) +""" + +testnoteannotation = """ +from reportlab.platypus.flowables import Flowable +class NoteAnnotation(Flowable): + '''put a pencil in the margin.''' + def wrap(self, *args): + return (1,10) # I take up very little space! (?) + def draw(self): + canvas = self.canv + canvas.translate(-10,-10) + canvas.rotate(180) + canvas.scale(0.2,0.2) + pencil(canvas, text="NOTE") +""" + +testhandannotation = """ +from reportlab.platypus.flowables import Flowable +from reportlab.lib.colors import tan, green +class HandAnnotation(Flowable): + '''A hand flowable.''' + def __init__(self, xoffset=0, size=None, fillcolor=tan, strokecolor=green): + from reportlab.lib.units import inch + if size is None: size=4*inch + self.fillcolor, self.strokecolor = fillcolor, strokecolor + self.xoffset = xoffset + self.size = size + # normal size is 4 inches + self.scale = size/(4.0*inch) + def wrap(self, *args): + return (self.xoffset, self.size) + def draw(self): + canvas = self.canv + canvas.setLineWidth(6) + canvas.setFillColor(self.fillcolor) + canvas.setStrokeColor(self.strokecolor) + canvas.translate(self.xoffset+self.size,0) + canvas.rotate(90) + canvas.scale(self.scale, self.scale) + hand(canvas, debug=0, fill=1) +""" + +lyrics = '''\ +well she hit Net Solutions +and she registered her own .com site now +and filled it up with yahoo profile pics +she snarfed in one night now +and she made 50 million when Hugh Hefner +bought up the rights now +and she'll have fun fun fun +til her Daddy takes the keyboard away''' + +lyrics = string.split(lyrics, "\n") +testtextsize = """ +def textsize(canvas): + from reportlab.lib.units import inch + from reportlab.lib.colors import magenta, red + canvas.setFont("Times-Roman", 20) + canvas.setFillColor(red) + canvas.drawCentredString(2.75*inch, 2.5*inch, "Font size examples") + canvas.setFillColor(magenta) + size = 7 + y = 2.3*inch + x = 1.3*inch + for line in lyrics: + canvas.setFont("Helvetica", size) + canvas.drawRightString(x,y,"%s points: " % size) + canvas.drawString(x,y, line) + y = y-size*1.2 + size = size+1.5 +""" + +teststar = """ +def star(canvas, title="Title Here", aka="Comment here.", + xcenter=None, ycenter=None, nvertices=5): + from math import pi + from reportlab.lib.units import inch + radius=inch/3.0 + if xcenter is None: xcenter=2.75*inch + if ycenter is None: ycenter=1.5*inch + canvas.drawCentredString(xcenter, ycenter+1.3*radius, title) + canvas.drawCentredString(xcenter, ycenter-1.4*radius, aka) + p = canvas.beginPath() + p.moveTo(xcenter,ycenter+radius) + from math import pi, cos, sin + angle = (2*pi)*2/5.0 + startangle = pi/2.0 + for vertex in range(nvertices-1): + nextangle = angle*(vertex+1)+startangle + x = xcenter + radius*cos(nextangle) + y = ycenter + radius*sin(nextangle) + p.lineTo(x,y) + if nvertices==5: + p.close() + canvas.drawPath(p) +""" + +testjoins = """ +def joins(canvas): + from reportlab.lib.units import inch + # make lines big + canvas.setLineWidth(5) + star(canvas, "Default: mitered join", "0: pointed", xcenter = 1*inch) + canvas.setLineJoin(1) + star(canvas, "Round join", "1: rounded") + canvas.setLineJoin(2) + star(canvas, "Bevelled join", "2: square", xcenter=4.5*inch) +""" + +testcaps = """ +def caps(canvas): + from reportlab.lib.units import inch + # make lines big + canvas.setLineWidth(5) + star(canvas, "Default", "no projection",xcenter = 1*inch, + nvertices=4) + canvas.setLineCap(1) + star(canvas, "Round cap", "1: ends in half circle", nvertices=4) + canvas.setLineCap(2) + star(canvas, "Square cap", "2: projects out half a width", xcenter=4.5*inch, + nvertices=4) +""" + +testdashes = """ +def dashes(canvas): + from reportlab.lib.units import inch + # make lines big + canvas.setDash(6,3) + star(canvas, "Simple dashes", "6 points on, 3 off", xcenter = 1*inch) + canvas.setDash(1,2) + star(canvas, "Dots", "One on, two off") + canvas.setDash([1,1,3,3,1,4,4,1], 0) + star(canvas, "Complex Pattern", "[1,1,3,3,1,4,4,1]", xcenter=4.5*inch) +""" + +testcursormoves1 = """ +def cursormoves1(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(inch, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 14) + for line in lyrics: + textobject.textLine(line) + textobject.setFillGray(0.4) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" + +testcursormoves2 = """ +def cursormoves2(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(2, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 14) + for line in lyrics: + textobject.textOut(line) + textobject.moveCursor(14,14) # POSITIVE Y moves down!!! + textobject.setFillColorRGB(0.4,0,1) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" + +testcharspace = """ +def charspace(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(3, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 10) + charspace = 0 + for line in lyrics: + textobject.setCharSpace(charspace) + textobject.textLine("%s: %s" %(charspace,line)) + charspace = charspace+0.5 + textobject.setFillGray(0.4) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" + +testwordspace = """ +def wordspace(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(3, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 12) + wordspace = 0 + for line in lyrics: + textobject.setWordSpace(wordspace) + textobject.textLine("%s: %s" %(wordspace,line)) + wordspace = wordspace+2.5 + textobject.setFillColorCMYK(0.4,0,0.4,0.2) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" +testhorizontalscale = """ +def horizontalscale(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(3, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 12) + horizontalscale = 80 # 100 is default + for line in lyrics: + textobject.setHorizScale(horizontalscale) + textobject.textLine("%s: %s" %(horizontalscale,line)) + horizontalscale = horizontalscale+10 + textobject.setFillColorCMYK(0.0,0.4,0.4,0.2) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" +testleading = """ +def leading(canvas): + from reportlab.lib.units import inch + textobject = canvas.beginText() + textobject.setTextOrigin(3, 2.5*inch) + textobject.setFont("Helvetica-Oblique", 14) + leading = 8 + for line in lyrics: + textobject.setLeading(leading) + textobject.textLine("%s: %s" %(leading,line)) + leading = leading+2.5 + textobject.setFillColorCMYK(0.8,0,0,0.3) + textobject.textLines(''' + With many apologies to the Beach Boys + and anyone else who finds this objectionable + ''') + canvas.drawText(textobject) +""" + +testhand = """ +def hand(canvas, debug=1, fill=0): + (startx, starty) = (0,0) + curves = [ + ( 0, 2), ( 0, 4), ( 0, 8), # back of hand + ( 5, 8), ( 7,10), ( 7,14), + (10,14), (10,13), ( 7.5, 8), # thumb + (13, 8), (14, 8), (17, 8), + (19, 8), (19, 6), (17, 6), + (15, 6), (13, 6), (11, 6), # index, pointing + (12, 6), (13, 6), (14, 6), + (16, 6), (16, 4), (14, 4), + (13, 4), (12, 4), (11, 4), # middle + (11.5, 4), (12, 4), (13, 4), + (15, 4), (15, 2), (13, 2), + (12.5, 2), (11.5, 2), (11, 2), # ring + (11.5, 2), (12, 2), (12.5, 2), + (14, 2), (14, 0), (12.5, 0), + (10, 0), (8, 0), (6, 0), # pinky, then close + ] + from reportlab.lib.units import inch + if debug: canvas.setLineWidth(6) + u = inch*0.2 + p = canvas.beginPath() + p.moveTo(startx, starty) + ccopy = list(curves) + while ccopy: + [(x1,y1), (x2,y2), (x3,y3)] = ccopy[:3] + del ccopy[:3] + p.curveTo(x1*u,y1*u,x2*u,y2*u,x3*u,y3*u) + p.close() + canvas.drawPath(p, fill=fill) + if debug: + from reportlab.lib.colors import red, green + (lastx, lasty) = (startx, starty) + ccopy = list(curves) + while ccopy: + [(x1,y1), (x2,y2), (x3,y3)] = ccopy[:3] + del ccopy[:3] + canvas.setStrokeColor(red) + canvas.line(lastx*u,lasty*u, x1*u,y1*u) + canvas.setStrokeColor(green) + canvas.line(x2*u,y2*u, x3*u,y3*u) + (lastx,lasty) = (x3,y3) +""" + +testhand2 = """ +def hand2(canvas): + canvas.translate(20,10) + canvas.setLineWidth(3) + canvas.setFillColorRGB(0.1, 0.3, 0.9) + canvas.setStrokeGray(0.5) + hand(canvas, debug=0, fill=1) +""" + +testfonts = """ +def fonts(canvas): + from reportlab.lib.units import inch + text = "Now is the time for all good men to..." + x = 1.8*inch + y = 2.7*inch + for font in canvas.getAvailableFonts(): + canvas.setFont(font, 10) + canvas.drawString(x,y,text) + canvas.setFont("Helvetica", 10) + canvas.drawRightString(x-10,y, font+":") + y = y-13 +""" + +testarcs = """ +def arcs(canvas): + from reportlab.lib.units import inch + canvas.setLineWidth(4) + canvas.setStrokeColorRGB(0.8, 1, 0.6) + # draw rectangles enclosing the arcs + canvas.rect(inch, inch, 1.5*inch, inch) + canvas.rect(3*inch, inch, inch, 1.5*inch) + canvas.setStrokeColorRGB(0, 0.2, 0.4) + canvas.setFillColorRGB(1, 0.6, 0.8) + p = canvas.beginPath() + p.moveTo(0.2*inch, 0.2*inch) + p.arcTo(inch, inch, 2.5*inch,2*inch, startAng=-30, extent=135) + p.arc(3*inch, inch, 4*inch, 2.5*inch, startAng=-45, extent=270) + canvas.drawPath(p, fill=1, stroke=1) +""" +testvariousshapes = """ +def variousshapes(canvas): + from reportlab.lib.units import inch + inch = int(inch) + canvas.setStrokeGray(0.5) + canvas.grid(range(0,11*inch/2,inch/2), range(0,7*inch/2,inch/2)) + canvas.setLineWidth(4) + canvas.setStrokeColorRGB(0, 0.2, 0.7) + canvas.setFillColorRGB(1, 0.6, 0.8) + p = canvas.beginPath() + p.rect(0.5*inch, 0.5*inch, 0.5*inch, 2*inch) + p.circle(2.75*inch, 1.5*inch, 0.3*inch) + p.ellipse(3.5*inch, 0.5*inch, 1.2*inch, 2*inch) + canvas.drawPath(p, fill=1, stroke=1) +""" + +testclosingfigures = """ +def closingfigures(canvas): + from reportlab.lib.units import inch + h = inch/3.0; k = inch/2.0 + canvas.setStrokeColorRGB(0.2,0.3,0.5) + canvas.setFillColorRGB(0.8,0.6,0.2) + canvas.setLineWidth(4) + p = canvas.beginPath() + for i in (1,2,3,4): + for j in (1,2): + xc,yc = inch*i, inch*j + p.moveTo(xc,yc) + p.arcTo(xc-h, yc-k, xc+h, yc+k, startAng=0, extent=60*i) + # close only the first one, not the second one + if j==1: + p.close() + canvas.drawPath(p, fill=1, stroke=1) +""" + +testforms = """ +def forms(canvas): + #first create a form... + canvas.beginForm("SpumoniForm") + #re-use some drawing functions from earlier + spumoni(canvas) + canvas.endForm() + + #then draw it + canvas.doForm("SpumoniForm") +""" + +def doctemplateillustration(canvas): + from reportlab.lib.units import inch + canvas.setFont("Helvetica", 10) + canvas.drawString(inch/4.0, 2.75*inch, "DocTemplate") + W = 4/3.0*inch + H = 2*inch + Wd = x = inch/4.0 + Hd =y = inch/2.0 + for name in ("two column", "chapter page", "title page"): + canvas.setFillColorRGB(0.5,1.0,1.0) + canvas.rect(x,y,W,H, fill=1) + canvas.setFillColorRGB(0,0,0) + canvas.drawString(x+inch/8, y+H-Wd, "PageTemplate") + canvas.drawCentredString(x+W/2.0, y-Wd, name) + x = x+W+Wd + canvas.saveState() + d = inch/16 + dW = (W-3*d)/2.0 + hD = H -2*d-Wd + canvas.translate(Wd+d, Hd+d) + for name in ("left Frame", "right Frame"): + canvas.setFillColorRGB(1.0,0.5,1.0) + canvas.rect(0,0, dW,hD, fill=1) + canvas.setFillGray(0.7) + dd= d/2.0 + ddH = (hD-6*dd)/5.0 + ddW = dW-2*dd + yy = dd + xx = dd + for i in range(5): + canvas.rect(xx,yy,ddW,ddH, fill=1, stroke=0) + yy = yy+ddH+dd + canvas.setFillColorRGB(0,0,0) + canvas.saveState() + canvas.rotate(90) + canvas.drawString(d,-dW/2, name) + canvas.restoreState() + canvas.translate(dW+d,0) + canvas.restoreState() + canvas.setFillColorRGB(1.0, 0.5, 1.0) + mx = Wd+W+Wd+d + my = Hd+d + mW = W-2*d + mH = H-d-Hd + canvas.rect(mx, my, mW, mH, fill=1) + canvas.rect(Wd+2*(W+Wd)+d, Hd+3*d, W-2*d, H/2.0, fill=1) + canvas.setFillGray(0.7) + canvas.rect(Wd+2*(W+Wd)+d+dd, Hd+5*d, W-2*d-2*dd, H/2.0-2*d-dd, fill=1) + xx = mx+dd + yy = my+mH/5.0 + ddH = (mH-6*dd-mH/5.0)/3.0 + ddW = mW - 2*dd + for i in range(3): + canvas.setFillGray(0.7) + canvas.rect(xx,yy,ddW,ddH, fill=1, stroke=1) + canvas.setFillGray(0) + canvas.drawString(xx+dd/2.0,yy+dd/2.0, "flowable %s" %(157-i)) + yy = yy+ddH+dd + canvas.drawCentredString(3*Wd+2*W+W/2, Hd+H/2.0, "First Flowable") + canvas.setFont("Times-BoldItalic", 8) + canvas.setFillGray(0) + canvas.drawCentredString(mx+mW/2.0, my+mH+3*dd, "Chapter 6: Lubricants") + canvas.setFont("Times-BoldItalic", 10) + canvas.drawCentredString(3*Wd+2*W+W/2, Hd+H-H/4, "College Life") + +class PlatIllust: + #wrap the above for PP# + def __init__(self, x, y, scale=1): + self.x = x + self.y = y + self.scale = scale + def drawOn(self, canvas): + canvas.saveState() + canvas.translate(self.x, self.y) + canvas.scale(self.scale, self.scale) + doctemplateillustration(canvas) + canvas.restoreState() + +class PingoIllust: + #wrap the above for PP# + def __init__(self, x, y, scale=1): +## print 'Pingo illustration %f, %f, %f' % (x,y,scale) + self.x = x + self.y = y + self.scale = scale + def drawOn(self, canvas): + canvas.rect(self.x, self.y, 100,100, stroke=1, fill=1) +## from pingo import testdrawings +## from pingo import pingopdf +## drawing = testdrawings.getDrawing3() +## canvas.saveState() +## canvas.scale(self.scale, self.scale) +## pingopdf.draw(drawing, canvas, self.x, self.y) +## canvas.restoreState() + +# D = dir() +g = globals() +Dprime = {} +from types import StringType +from string import strip +for (a,b) in g.items(): + if a[:4]=="test" and type(b) is StringType: + #print 'for', a + #print b + b = strip(b) + exec(b+'\n') + +platypussetup = """ +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer +from reportlab.lib.styles import getSampleStyleSheet +from reportlab.lib.pagesizes import DEFAULT_PAGE_SIZE +from reportlab.lib.units import inch +PAGE_HEIGHT=DEFAULT_PAGE_SIZE[1]; PAGE_WIDTH=DEFAULT_PAGE_SIZE[0] +styles = getSampleStyleSheet() +""" +platypusfirstpage = """ +Title = "Hello world" +pageinfo = "platypus example" +def myFirstPage(canvas, doc): + canvas.saveState() + canvas.setFont('Times-Bold',16) + canvas.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, Title) + canvas.setFont('Times-Roman',9) + canvas.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo) + canvas.restoreState() +""" +platypusnextpage = """ +def myLaterPages(canvas, doc): + canvas.saveState() + canvas.setFont('Times-Roman',9) + canvas.drawString(inch, 0.75 * inch, "Page %d %s" % (doc.page, pageinfo)) + canvas.restoreState() +""" +platypusgo = """ +def go(): + doc = SimpleDocTemplate("phello.pdf") + Story = [Spacer(1,2*inch)] + style = styles["Normal"] + for i in range(100): + bogustext = ("This is Paragraph number %s. " % i) *20 + p = Paragraph(bogustext, style) + Story.append(p) + Story.append(Spacer(1,0.2*inch)) + doc.build(Story, onFirstPage=myFirstPage, onLaterPages=myLaterPages) +""" + +if __name__=="__main__": + # then do the platypus hello world + for b in platypussetup, platypusfirstpage, platypusnextpage, platypusgo: + b = strip(b) + exec(b+'\n') + go() diff --git a/bin/reportlab/tools/pythonpoint/demos/figures.xml b/bin/reportlab/tools/pythonpoint/demos/figures.xml new file mode 100644 index 00000000000..b3314ed8298 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/demos/figures.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- edited with XMLSPY v2004 rel. 3 U (http://www.xmlspy.com) by Andy Robinson (ReportLab Europe Ltd.) --> +<!DOCTYPE presentation SYSTEM "../pythonpoint.dtd"> +<presentation filename="figures.xml" pageDuration="10"> + <stylesheet module="standard" function="getParagraphStyles"/> + <title>New Feature Tests + + Andy Robinson + + + Reportlab Sample Applications + +
+ + + +
+ diff --git a/bin/reportlab/tools/pythonpoint/demos/htu.xml b/bin/reportlab/tools/pythonpoint/demos/htu.xml new file mode 100644 index 00000000000..56cb92457dc --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/demos/htu.xml @@ -0,0 +1,96 @@ + + + + +
+ + + © 2002, H. Turgut UYAR + + + + + + New features in PythonPoint + H. Turgut Uyar + uyar@cs.itu.edu.tr + + + + + TrueType Support + + + PythonPoint can read UTF-8 encoded input files and produce + correct output (provided your font has all necessary + characters). That enables you, for example, to have native (in my + case, Turkish) characters in your document: + + +  0 ^  1 _ + + + + + + Effects + + + Paragraphs, images, tables and geometric shapes can now have + effectname, effectdirection, effectdimension, effectmotion and + effectduration attributes: + A paragraph + + + Col1,Col2,Col3 + Row1Col1,Row1Col2,Row1Col3 + Row2Col1,Row2Col2,Row2Col3 +
+ + + String + +
+ + + Printing + + + Be careful when using effects: A new slide is created for + each effect, so DON'T print the resulting PDF file. + new command-line option: --printout + produces printable PDF + + + + + New Paragraph Styles + + + Bullet2 - Second level bullets + Here's an example + Or an example with a longer text to see + how it wraps at the end of each line + Author and EMail + See the cover page for examples + They have to be in the style file, so either use + the htu style file or edit your style file + + + + + ToDo + + + Unicode chars in the outline + + +
+
diff --git a/bin/reportlab/tools/pythonpoint/demos/leftlogo.a85 b/bin/reportlab/tools/pythonpoint/demos/leftlogo.a85 new file mode 100644 index 00000000000..7d55afcf348 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/demos/leftlogo.a85 @@ -0,0 +1,53 @@ +BI +/W 103 /H 68 /BPC 8 /CS /RGB /F [/A85 /Fl] +ID +Gb"/*cYjIe']/l*^;)UQj:Ec+nd!,-,*G/5;0"E.+@qg.\92h2HbiE)jC5*M +$a,@:RoXB@WPjMu=[F;88l[?@OO=6qV,HNNiL#(cac+!&rF)2^DHJ?_:WQkn +1[fb`hOBr--B>FoSDXMt35Gnj6n\3XT8sf/Y%-NPLqg'pmQ+*EiV6D:,$akf +!i2MA/A-"n[R.R)S!ePi$u('8mrCG8[3:WLlB[amBYAS4hR2*f7tR;&\%]Ld +ZX5^f6Qj@\h7^FuQ(">[ml32FWhJG;Z)t2oADV4[H6GbW.mH`MDlesQZO5+Q5)]OWf2G9[ +U9W99*2'#PD+cIXnp"J"(BrY^=(rg;ITf("c#0"S<;fjK,APV+p9p+05LO&X +jQNf`8c26f]J54N'k#I9=Bt4]e&6gN'#S[nK3*VhQ!`PpXcJ0i:J_2rC@=>X\5dUbEcCZ?\6O)+F/35ZJD6R$H^-Z!R +5;8=l//9SUkC;LhQ*?di<'6e&/BNIqm(%(SDYXJ +p%%Z+J.IshkX$tLPOD'"jSV#/6iY?QI>8P-%o0k3`e#X33RJS`f'D!D2rTJ- +(4Znb03&koo>X[bKs.qpBOkU,KCCO3S$2.Gr@e8E=$On&hUiAB2[.;G@FQf7_K1&D6RJ.Ih@-56Ss&B1q:\)MgJ8P4Sa-(Fm +J"d8Z]Mf`Q$G^\tK@pQWQs3iRB[KP=+H&@+iH@XGFQ'#n!DJ$)Mc?"U#od!oYl3En7nT"7UC,,Q&Hr +9.SPlHak6s)J0;k;hQC:k82'=COeskp&dB-#@RrY1B=H^'L=,;9uJssq%XGf +h/fXmq>1G\?k6ZH*GSk&M)?&TJ?rYW.9f!gdM2&:Kr51A3^MX?GV'T@$h)Da +-RIIcpl8fhiQr=`\k[7;3`X"d!or(jb1YM>"Ppi`,67@]M"d ++mXd;WRE"IKX@n&dNNV/55(]`==+_r?K5bp?$^4Pn/[2ncL(/l#CfSYAg:iN +@3j5R$qtm;AIF7k%^ak'%4Z1354d"1VT\g]cm[^hI.Yf>DcLS\rq+As2F?LI +'gq+/_o9&kK,4@]qbdfA8FauP;!N'$5J@1P/\Th=9i^\b$/-6-kL#$E&39t% +/O[1Fm`/F?!q;%bB')DM`KY&D!,6)lG5TQ88ejHiTWKt;3_8:@M!NnP'D2s$ +2Q25"dX)7pQ%VUq+r1%.iMQiLR#&.@\:Pe96FEbkgLu`Fb;!#d,DPC-H/d?u +^']=P86?6qr/@_/T-T0?,+>brI`?=13["Y(3DYSMX7UAUk0'!l!aPN4$e\f&(i2TTrbVuOYhc05Rko:?UP09gLc(-/ni'Q6T.B&8]k +#a_e\0FTMV`IT;W7M>_'NT_f=~> +EI diff --git a/bin/reportlab/tools/pythonpoint/demos/leftlogo.gif b/bin/reportlab/tools/pythonpoint/demos/leftlogo.gif new file mode 100644 index 0000000000000000000000000000000000000000..2d9c990ba7aa34a17f5fc2f36c931a4f300aec76 GIT binary patch literal 2198 zcmV;H2x<36Nk%w1VP^nD0OkMy|NsB?_V)1b@bB;U=H}+%;o;ui_uk&{*Vot2&-KpE z@6OKV&d%V>%*@5b#lF7rzP{eQz30BZ)x5mWy}iA+x8Jw7x3917uCCm!uGX%u&8w@c zpP%fWp4^_E)}NolnVHOwkLHe!(~ywCj*iWbkGz0@=6`?XfPmJ2f7*V2&wzluetyM% ze!G5tw|;)Gc6Q5faNuum-fwTcZ*R42ZNqMEt!8G-XJ@xyVBKbBnO|SOTwJ73P~1;X z&`?moPfxT8Ka@W|i$6bw zGc(LEFxD_Iy)!eJFfg$!EWj@>k}ofPEG&^AAkrWp#V9C-ARxCOAgLfAo**ETA0Le$ zAA27ka}W^D78Z{X5WW=^gb)z35D=yi5S$MWk`NGu4-b3~4|5L>Yybev006}R0KEVJ zwEzIE005-`0GR*)i~sgoTEOh>41ejE#`iL;FkuiROfhk{CNKV00a6110WlK53W4ekr#(vJxZ?X z+~L`j%RHT&xJNqc2Gsm)}OLHt*jnE@ThpQnV<=7etDMqUf zDEpbKL$s*TqeL;ZGWe>6ojTqc@_fXhRgF9Up3<44tgF|r$`rU_+Nuejr`|+PRrH{h z60H9Ja{ii2x9(h65MG7Jb7!H>wT)b*fqMsPL$!%Y!s^Is0LQa+mLUdAD9TcOXXQ;^ z1V;`XlzN=BlCVb)U&EUzzahpd0!n{=Qzz>Dd7q0`B}(floK`iWXRK7b#sgAnB-#@3 zWN5{Mr#Mk5Kh1tN-t9Qc4BuAtFUNQXqx;R75E)-i*9T}0r@06o+af=U0yBL@_;0x&?5 zaF_>x0p=O-qAN=1N*zle{o2@w0LYLmu&i_tm6C_J5&;Ry0sz4pWHqKm9IU?L0Uc75 z3r!hXz;b~Pu&7b)6fOOrixKt8LIqcc9de*HSKP)56=$>p!yNKT3%~+(Nc_tG5B@E6 z0IWG!FAE27gXZR$^mEG5UL8TL?9@4 z9<;&&9!+Qj5EWGRyi^@PvjW5&Os#;He1=%tCsR7?vBy#@gdKJgNn8emMj3TWN7Mk| z04M-8SZPOItT2&>!V|BcHxEk$O@|PzWN^oFtbDOXxi&z?)gi2UP&6x~?V;=dU(f)- z3Pa$r!pVpeq>(FZpqzw+uB?#~k+5bPsM6L*k6CCXxmwsW}*xKY}3h8TmQke~)UY{3Z#JRd)B-~kA(8^(k zEo=b`WWWkV_`(u@vs7*(Bma+F~-x#qlhY!TT6~|j{3D?24s)NIE##jjvR*n_xLicB)W-au%SO^61JptLe>k$|{=Ku?sNv z^NT7FfB*%UfFz;vtp>@nO8~7!5ClNb3djv4QIV%Y73!rT1{7RxXiyLWK+p^TlVu`# zXh822fQ|}>T3penNFVA6-~<2$-09_$F2SlFc(O$WS`{k-K!i~d$R6i`4NMDEt}a+9 zQkbYuEAA7Smc>-4tT-r}Dsi9$!DgxRY3fU%ItdG5NI^IAEs#}*QyimK0$U}b;!fwo zrw^HEtdpn_E8cQeJIzO}0AQMhuW9FGAhw zj!|)WEl_qwIh^Wx_q$>R(sQ%(#+R7)yy!JAQK0!;=+aV9x{R-U=POK|7?v{Rou+>G Y%U`dMsl5^P=RyZeV7hck6@dT%J4FQNyZ`_I literal 0 HcmV?d00001 diff --git a/bin/reportlab/tools/pythonpoint/demos/lj8100.jpg b/bin/reportlab/tools/pythonpoint/demos/lj8100.jpg new file mode 100644 index 0000000000000000000000000000000000000000..be3c6183d3b1cc5fd964caaf427007f9974844ad GIT binary patch literal 12463 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 + + + + + +
+ + + + + + + + + Printing with Python + + + + Andy Robinson, Robinson Analytics Ltd. + + + O'Reilly Python Conference, Monterey, 24th August 1999 + + + + + + + Background to the project: + + + London-based consultant and corporate developer + + + want to do neat Python stuff in the daytime + + + working for many years on financial modelling + + + this is one of 6 modules in that system + + + quickest to deliver, offers very wide benefits + + + 25% of architecture done, but already very useful + + + Release early, release often! + + + + + + + + Goal: + + + A Reporting Package on the Next Curve... + + + Report on objects, not databases + + + Scalable to million page runs + + + Light enough to embed in any application + + + Allow reuse of graphical objects across reports + + + Open and extensible on several levels + + + Publication quality + + + Support all the world's languages - one day + + + + + + + Portable Document Format + + + The New PostScript + + + Free readers on all platforms + + + Better than paper - view it, email it, print it + + + 'Final Form' for documents + + + High end solution - no limits to quality + + + ...but you can't learn it in Notepad! + + + + + + + + + PDFgen and PIDDLE + + + + + + + Layer One - PDFgen + + + makes PDF documents from pure Python + + + wraps up PDF document structure + + + exposes nice effects - page transitions, outline trees (RSN!) + + + low level graphics promitives (postscript imaging model) + + + Fine control of text placement + + + Supports Asian text + + + Supports coordinate transformations and clipping + + + ...a foundation for other apps to build on + + + + + + + PDFgen Image Support + + + Python Imaging Library and zlib do all the work - many formats. + Images cached (like .pyc files) - very fast builds possible. + + + + + + + + Layer Two: PIDDLE + + + Plug In Drawing, Does Little Else + + + Easy Graphics Library + + + Abstract Canvas Interface + + + Pluggable Back Ends + + + Same code can do viewing and printing + + + Standard set of test patterns + + + Uses Python Imaging Library + + + Back ends includeTkinter, wxPython, Mac, Pythonwin, PDF, PostScript, + OpenGL, Adobe Illustrator and PIL. Really easy to add a new one! + + + + + + + Layer Three: PLATYPUS + + + "Page Layout And Typography Using Scripts" + + + Trying to work out the API now. Key Concepts: + + + Drawable objects - can 'wrap to fit' + + + Frames on page + + + Frame consumes from a list of drawables until full + + + Document Models e.g. SimpleFlowDocument + + + XSL Flow Object model may be a good target + + + + + + + Drawable Objects + + + Next layer of PIDDLE extensibility. + Each draws in its own coodinate system + + + paragraph, image, table + + + chart libraries + + + diagrams + + + Open Source - let people contribute new ones. + Anything you could have in a view can be a new + drawable type. + + + + + + + Style Sheet Driven + + + Styles use instance inheritance + + + Paragraph Styles - Style Sheet Compulsory! + + + Text Styles within a paragraph + + + Table and Table Cell Styles + + + + + + + Vision + + + XML to PDF in one step + + + Publish to web and print from same source + + + Financial and Scientific reporting tool + + + Embedded reporting engine + + + Volume reporting tool for business + + + + + + + PythonPoint + + + How I made this presentation... + + + +
+
diff --git a/bin/reportlab/tools/pythonpoint/demos/outline.gif b/bin/reportlab/tools/pythonpoint/demos/outline.gif new file mode 100644 index 0000000000000000000000000000000000000000..4a294b592776088abf34d7ec109dc6f0e2af4210 GIT binary patch literal 12918 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%0AxFJg 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 literal 0 HcmV?d00001 diff --git a/bin/reportlab/tools/pythonpoint/demos/pythonpoint.xml b/bin/reportlab/tools/pythonpoint/demos/pythonpoint.xml new file mode 100644 index 00000000000..15d1ad709b5 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/demos/pythonpoint.xml @@ -0,0 +1,1051 @@ + + + + + PythonPoint Demonstration + Andy Robinson + Reportlab Sample Applications +

+ + + +
+ diff --git a/bin/reportlab/tools/pythonpoint/demos/slidebox.py b/bin/reportlab/tools/pythonpoint/demos/slidebox.py new file mode 100644 index 00000000000..f0d9cf308e9 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/demos/slidebox.py @@ -0,0 +1,29 @@ +#Autogenerated by ReportLab guiedit do not edit +from reportlab.graphics.shapes import _DrawingEditorMixin +from reportlab.graphics.charts.slidebox import SlideBox +from rlextra.graphics.guiedit.datacharts import ODBCDataSource, CSVDataSource, DataAssociation, DataAwareDrawing + +class SlideBoxDrawing(_DrawingEditorMixin,DataAwareDrawing): + def __init__(self,width=400,height=200,*args,**kw): + apply(DataAwareDrawing.__init__,(self,width,height)+args,kw) + self._add(self,SlideBox(),name='SlideBox',validate=None,desc='The main chart') + self.height = 40 + self.width = 168 + #self.dataSource = ODBCDataSource() + self.dataSource = CSVDataSource() + self.dataSource.filename = 'slidebox.csv' + self.dataSource.integerColumns = ['chartId','value','numberOfBoxes'] + self.dataSource.sql = 'SELECT chartId,numberOfBoxes,label,value FROM generic_slidebox' + self.dataSource.associations.size = 4 + self.dataSource.associations.element00 = DataAssociation(column=0, target='chartId', assocType='scalar') + self.dataSource.associations.element01 = DataAssociation(column=1, target='SlideBox.numberOfBoxes', assocType='scalar') + self.dataSource.associations.element02 = DataAssociation(column=2, target='SlideBox.sourceLabelText', assocType='scalar') + self.dataSource.associations.element03 = DataAssociation(column=3, target='SlideBox.trianglePosition', assocType='scalar') + self.verbose = 1 + self.formats = ['eps', 'pdf'] + self.outDir = './output/' + self.fileNamePattern = 'slidebox%03d' + + +if __name__=="__main__": #NORUNTESTS + SlideBoxDrawing().go() diff --git a/bin/reportlab/tools/pythonpoint/demos/spectrum.png b/bin/reportlab/tools/pythonpoint/demos/spectrum.png new file mode 100644 index 0000000000000000000000000000000000000000..06747655056873c295693ed3f2e8db42f6909ad1 GIT binary patch literal 1855 zcmd5+`#aMM9Ny}jd@DQK>O7WvtS3&nozv6FWvmt!38C~1lOr7#=5A)DPTy0=B_$-6 ztSk30TFKE>1h2RDBQB( zrJT~TIZxfJHLA%9id$TmnnP{B@-2FuGu6nQqsgV4^o^Kyb74*2AT41Ue7p#^nU(0GkAxOZoJ0?=9onEO_F3 zp)^WfHGK!`oro>$Gi4DUaJ#UxjmB@_bvVGUMY)Me-5o<8W@P&yGaM=Q6~OEX@#prYA-52e%raCZ((?Q zo~MEdcrZLl1hMA!*>71=nxC2f_Y#lx{|mRDXPe;prM-1Z17I!V-U_`GhGa%cL?6PCtHpQD zN0j%)7Hku39Kj_`n9#xz>^DL(t8Z%);R{Q8mC`qp$k9LFw0EMl@bOb+ZLtk`1#Ffb z;OB22h;qdDq=sFY^SQjSARM+4&M3u;^)Wn?C}Cz)1(ZS_EIsZ4_M?%w5df)k4Min$ z3*L3c7Oycc1*2^NpZ%hfJWa1%leHJMlC|-a6(7ULv*x&V+5Ev{5Z}c$zk@hHVv1wA z?i%z3F`|DIr9QJqh&phkN~nIrUd8>|TLsXj%-{K>6G%=4A)_C1g;I@w{*uJlJMrI5 zv@M9>1%fffRi7~sSeLe+V=0Iq&Vc^ZfL8TnD?)nL zVI}eO^SmTPx{-P@w5cUJCTq3S>dE$n`v}esq{j8CF;bLzGRthYUNHL$k@Q4GYM1dt zP~o6f+ye{NDTV^rW4l=cJOoKHu@X`Rz)n)`I*hdN@XnHj+MP%7fsmR~He*6)720iy zB*BSStwytvJuiSFKhMOJeuy_WR}%Beh0*S*KO`We*E?o>vhZE81xLNO;r#dESh)*5 z!!^FHl8hb_%%|Rn{n*VB1B^yXiPYfZYh#?WdYW1Bv_`D8-XmWCCFX*cZv}oID}n9z zW|Bjs6_ydlK#S5lbnBGvb%1?*cE2tc0e1jVr!nVRXm@*47Pp|XPPsbgepRT8oBSy<15s?Odh^%chn7hjPM=?)iIx|a|TaA}rax>>qKx^d~QrF-d??r>SUTPYO~5fBt9#oCYe ze9s?q<~(!n{l&dA=ggT=hpI_GvD*P;;QRpme?b5M1OR{q0096X1OP+;fLOr&9s~k_ zAOH|701g1aApkf60LT6ph=2eP2mk^LKmh5DfsLAwV<&h{gi%nPMS8ECPtVhXLHPyeD*zcMo%4@jm(f+Wi*f zzlDH6un;%^0*6502nZYtLELM)-$pUiSU4O2heP0S1RRcqBkp~=heE&+ zSU3s*M?v5y1RRBhqXBR<1dc|)(OCGsqxYn+2srlMBfx)*{xkGm58&R8d*%1S?)Cgf z^FN~Zae#Z6`-=Ao_i^`Ii2oKAfdU{<5CjT=Kw%MR00Ip`pb-c(7IELDd!hHSSS;$k zMfWQ20|8hR5P$+gP(TCI1W^X~DnSoD3{@145`2Vl`a02&BE0}*H-7JWYp_jcYh zf?&~b02&TK!x3mW7L5R)5s?3ABCu!_0F8p6Q3x~&i$(*`Xb2jOK%=qf`)Rsw$h{^k z_Pia1_-~l*=jMJy?kC{C`GP?HQwjO6N%#G@xBlMQ|Ar0$`A<6HKk@fE?Fwe+F&jiC}<)N#4K#H()Tp){qTjvUYy%@*A_R?E6^Zl=yZO; ziu2p&;fHlQv+KHBN zbQT2WX6)OS8A3r3fm;R~$!Z6w6zrAMF4(RFGcT&iX@)ci7*|QR!^h0b1>{dqm63vX z;u!Lxh7jlj^-nl3&=xCvn{D-|`Z2((EyyZpD(;3nCfTjuDc@>a17zd3byJ9-6s~hN z_w<#iww590tt$lBhwzkz$U;q-aWtv+(&M~(4pDlvOrWPh#^#@5+Wu1SA!P6f8w`;+ zNy%Qx$pI|ZJN0I}2KYC=V@mepSj-5R&z%?8^_RdhSd=VDcvcO{$a4@^r}HX`07Zw(66x@944MQ++Q zAzAXqa($D9EOwXf$HJRNW7n*`yRe!x0hi8q^^UP#B&R<? zJQ3bVmeb@YdHFD2fainv@0L6Lvue$|z;0p-u@4`Fp0AuJZ5VbI0;$aFf(-K?Jvmnt zdw1hpb@l!#ZK#v~_ZfP}-`u))BvSAoX=i9Cc;RgqKVpYArO_ofI<5a?Ye|LAieu@E z{MoFCt%A;ZB*SB~jg*&vfAgx29n{eF9>f0vm_K~Ga6w9dTwZc_()E9xeDWYU4Z)p7 zW;b#p={uYrK|b&pSIl1kenJ>hFvE&|G5N7dQhz)` zgkNDf*_6h@Y&%m`XoSM^8p+N;8_l&%p{viJVnU?TrxttBVlC_Vm@3NwYbwkySLhWTG6Se&v*EAJuQBWkAo&ta<32b)yxZe zDG1<)7%Fhnrq*B3g^>^Cfxw2#tf9qaxEE+^vXViH?6yPppMXjCkfA)*|ElC6BiA9y z6x;Nm<_y*zTW8HrC$8nz?)5H=M{a*b`y5DMM#@no(88r3fe+Ty{jHAN_G?OuO38m1 zpielADMJp!BE;rD%cRtaEN*hxJexaQHsW5jp)+zOO(CKF1jh7AT$ju|HOZx*rPqOi z{Tbrw)P$TF4zyFkM26;d!tP$#`>F{)uqT(9Yfvj0(bdJAQ0M-<&8?ZGD#8fnS_KkF zwT2?&%-mz3G6{+V&cao?G4(S>PM{GOxUkHVoRT)sKu0IQqtu8=TmwzoP<^G(UE|hE z!)O+b$4ysA$wX)dD=j9ZHt#Onn_G4tN8R#^CML#d4jKztu|HsLkQEb>J)ctDv^3vk?@N=MQW^JJg%$J(|*GJS(VAPFdiZxim+`x zNM#`r+H$#Lr!|QCSe-Urdqu?|I{Sixqx-^R9J=8dnkT*Gn?&2E2`r%M(V>`G7u~D! zR7TG79c;#lPuNjH9b6Vk9`YtkLq(s9s}_$TM|+9vD#A8GYgs4MR3EU2W|~!)tO~ol z_NviJoKEDH+WQH-?BGGxUN5nnrYBfM<6k#|fVZz+M94PR7x=EP#K`w!fs9H9V+Zz9 zj-wOkh!~Mfo3qyRh7FjmN)-dq;j00mnl{1{I;0ui@Y@BoiXXJ#B$U|X{-HpMRXj<1 zYW>_mf0iLaDsc%vtpJous)Fn?Oo@`nH}YznTiZ@$j_bEKSDi`1De~8)s5BigY8GC% ze@sFf0Swe`tCHhdOg^rz>;nkY#Qa-}ih9~ov%3it6f|U}y;-v|=pGbz#~oBUl=cdc$2?!OP`m==m{TkCNaD`|7sqD706N|(-~5IzWObqsg>?k z;r@%XRpTj7N3ox7ySh43|HQufV7j)`_ao)}*tMgYa-Qsw$fuh|2U}kO>BgSyJW!Ft zgfFOb4Af~BQ5nCa(F7V>Y4gam962N_>F#i9m)hCgQW9auylSHTL|wFZE@+qWAYfzL z{&68}K2Ewj?I=5zUH3HdfD3L^ISaUFzcmwkU>_J%UPWr z6`}ike0gC5A%HU%^^`)H)8oqxn}Zt>5QCpj0?2oTozMu(hab8;CM6^R+~;{Je!xwU z$1l)fJCPrWeydctp-tOs*J7+CIjyUn6L(YL#q8%RxXJ@C00j{~_3L%g-E_+!6m^zl z3v!T=ui}f~pj+brrfm995^4<4d4FS1JXGXnm*D9{Ch-soX%mEhHH}Vi)Si+^S{`L{ z&u46$^mfnnF6|4P>(ITUZK=kPtYBF^^6)7^EszeAJa%(q9nmO z1*()OOUU3Dcd(1FJ(0YvqhvKve8J;~Z9$K|Y zV?_1rEH=cnP@Dh^+Qm^R&PyLAfXf%=SzP)f*Hj4BZO%ZROI=9+6HZzxBCPcUJ0^<- zBPO~0Gd~~%82&>@^bQEaHxMrLOXlOydmHkLCob<&NhctE7gi`{z-t z+5dpBC@%z9-zINJs3O6Wk~&dzHJTxE%mGvKYQc5Bszmfac?qH|C809>s}fv<@&1%0 z`=c3rv<`&$FSQfn=C@ct2I28`ev>fp^hz?^pcEgOn~WpAOUJPXR%LNUPT-W$_ySYo zBfy_5W?)5h>Jb|ck!L5=BRJ_Q29E8NBK~0QXAd1Q8&B`SZ3gx3T`%3Dt#DED8mr`Abwyb*QA^-N?=ff>`YGy@|Wkg;&aR>dPlsNF9ZCzB9cB4et6xZLi%>LkDwL( zta%#w&d}sUDjNs)vhw3nNJFN&edG^ywqK)C0-xFSCOBE`T?1B}A|+*?+qkTOFO~Urzb=hKMCXb&8LUKpZFMQhr?sy4EB`Lc64?n z@3wzVBxNy{XtmKDgyH&|uf_W&zf!`h;HLxLL_EQ9k3y##yL$rhb!+GkP_KlNewU%> zt(BT&mU<1!+ogqRCsn!L18XVUt4Bo+#&2N16m$-YP9X*=&FQ|JZf#QUt1s^63h?5`aAjNx+i50*wca z2J`ecDqqWzc5uA9d7VGU%4`t=NiE1};GRb+InP=21X@1h^P3Ar)`b|eI{S)KPZY$u zerKPW2lGdVGIl@kO}tC){AI^T7nWoxen#uj-jn@?q@g9{Nzt5O>SQCIQ-5D0jFpPJ zlf$d2QOkxs7yFO8-i94Efk&sqRCF9ojFXTqmOpM{;`8lnvLi)QN?q_xZorD8^*c_* zFN562m5(V%d$XpS3U11wvzg15Fbfv*lw6Iz12v|gVFXmv784&Ertu|t`6Hc`T5rWh z2Zl~)*x5h;=)wRcVH4uCZPkuuX;%InSMfeefT+rQ$GOCvUL`9oda72ENT*U#zR$wa zu>9AkY8OV}TIl9e*U2B11P)kfH0Pb{(%BB!_7ZuF*SY=~er(JvzY*2O=ayMAwNNv0 zb1MfWrRqM1+UTq~UKN21`&$YaxI>~`lu$=W z(hEC$*n+nqg25$p4jYe!p2S=w|0GqO|D{R$nTI9bgqur|vB0H~+t3j+(?TSDh9=wy zOK@y?SvVT@PQ$Oil#@BsXz~Q6KW>kw^z;l(nprbrTd%&S`tmt}ij9;VaDKz|VA7Mj zR9v28Ym7AY#S0)wUztxlERe<7kMG^)jzE)lS#9SMUrxmXMZ7;574Ie=u*@p_h<2!# z?X+`etpVZu74G7BPVK8SB@g-#(XVZVJUq2>>q~idN(7<}3 z?%2}qm_yOAZ&ZifmvF5^9pJ-9YG->B-h+uiZd11IipE<9M%P9$4Mlkmd+)r10LRqq zq_m0eeLN(M8~PQbl1St87^9=1Ih(Njfxn;-~4flLm#C47hC{sSWTvmrbsc8_pC9 zTcJ~yDHrz%q<3U{@^_F`QZgt-Gc}#$lE`1(r5jfB!p)`A z&A*6@3aXA0!b&2y{pjBMIjjnOZJ`grJd*m`W4Iy8v}ptr*7!0S30xh5wD8s1 zQzlj>fmJt{12@5!pm_+cceN<&S`hLO4G++;3)zz-P~Dk6rwgKtFnz^sIG#n-Y`xML zcFP3dvcV62Wnnl=TsS`CXBHZoipg_t+^l3;6Y(L_N|ficB$OrXGGOSk?fnUVwkTFc z*-##wq8wZ1xXt4q&HKQ9vAp)|;YR^y>6c|(C{sk`f zlm&IEk~0<7Sgxw@F09Y>CY=7< zGEyB@?Qi{{CSH-eHn9FAb&Jvah!ZFeLu|n zSZW`2iyz^-p<}!iqOIfY?{o5b-FcArZBa$e+BJls)Bl*ia1@6fW3u&J>S)QryOL%H z@IY@55{*a3rV^aV%4i7yfS^j4eH8!|pRD$(Gc%DT66u+}-kv5zh5ZNp)BC-=MT${8 z{SN_T6*~<=0;)lFD+gmB@?M>;5Lo?UUQf@KtlM0@P@+7KQb}K@jusVCzi9ma{oCY~ z^&mGcvx(rS8dhO;_wGf#MH#E1=Js?~nraj6OB3V9uC$F|P~^WVCYbB`s4l^?1qNTs z;vCJ4Xk1Uq%5f=rKZDyM^DF0U2lc;*V40() zR=2{XejBG#=Vd<%Vzt)$TftLvc<(YrgQxUjheEDU`*)(oMo&9(D1Ib{xJvl-HF*BC z5obw$&dmP2n&;$qeN8HU*hA{PC+wK5uFtncz!Fsm>9;N)Z$m&kWIlujUPcHGhCgE<7Y~~v^FDxrb z?l);-@4*2~%-lp(a2MZ7quk|mv!mER$|g*YORP^4YT4V!)1P}>SF4MgIh3fvm| z)Iw|39vq`FsDtNlj?uK$6p;J))1qg}ysDbFnloskR|O+mNeTKbmAcweq_OSF*~3zq zWyj1)=h0}Svry09X~OZ1J4UtIWKV7*#;<)X=Q4#86ydPdwdvd;1lq1df;Zk>U>0kA z$o?!IiTL2B?C#xe>sFOXX?nI>l{4vXBCF|WPm{IZy|SO2$C`_{xu8gpS|IO0Vq-XMk9!KRrQc|{N} zwZ^d<8+9OzWr}e|dUVQk zmVlw*QbWOeSJ&=G?{dKn%*d4=-cdZfx5O{yiNesSOeYYfcSe8SIu(0KO%GXwKJr>r zLKcjxL7_V=Q=s+iC%H!fL7jQOo==WMiB?(Z%+aR!Qf1GGPJm)Lh`xZR`El%~b??4g z7#Ur?QpCHk&|T9Dy_L+$kx-T494KCA3l=5P&5XY2(^g{frf@P96CGdv#8BH~MLOKQ5&>^_RL)WbyTvIYl=ViqFfLIa%06ht667h=$3yfirx-P}MgGB>pNR;`{NkI@+)SXTwF`Op~=k zEthVbY}M8^iIPDK$@-dNS+bFDqk)?f7?Ydm_{Y7Pd@D$!|AI?o&}}yA)!{2bJ}us_ zurw_k9%{<0M$luv8slJzNun)WkoFwib{?qMY}UZnA#~cvBA2xgRtr*Nq*<0z0cR$; z^LT$JRF?yR-WM#2zHZbmgmh%R&TZuSW3Q?19LzRov{r=tZ8#ujQ0v9WUjW5Ts*6Tq z45I3|{q53n&8{TskNtQzDd9hn#R8E#dwZ#WL|*lBpO}i>6;sXduMW zJ9Y8ITw5}qaowfu?~PNxk3BT~TdW^(+@Xq;HSpb*cC>w!uY3JRMT~EBHtY?CedRkY zFR%U5gFB*i7TGInnSP#?o*w3FR_AI}AwIm}p7!BB=OcGXz}y9k|s+vF+w(JdL& z^LH!7AVGR47hj|?jxsnR7wBa^kb&|9dpevi$>Hw$~AukhKAl79a=Y7$l$FB(iE z;Umh?>QQ8T9MgJN_Q+_P%bRiVi*<*Nvxs2SYSX-h|xM(Q+XcPFbPFA+7mFm%nzN@m`hxR2!WF<@L@Rc~d!lde5 z5}z2>o)>bV~L+kWO=9km=XROY2r`?*)N_HI;2A3r_Mo$JGxj{3hNJ{2N zw_54t*^IJxm2Xu)f{gt*LQknhIA{gu-Yn3^R|ndjZ}W&vF#5S|O~tP?!-gd(8-~~n z`To@hCE(*ow7n|Ulnx-A33(*^8z2K)c>i7RInFW`YF6`%QK_&0Cljd+lF-dSPu&-h zCGtkYkTFFxUalm@?KrdYl!2YtgEHRFPHXr}Gcb^AiTGk(bi8pA{^=PfjLL8Mh)2>} z5;_zg*xGt`h~w2S^H!PH=y8_-^#^E%V)uMim5%$kQk*3O%(}pjM?XUQc{0}gYQ&&x z5%wTxcMzXVVzwKm+UOxxnnGVowjmhlAY)nirM&N(wfmm=M?q<)wX-AVyiC_mn&aV2 zy)o3(8h6k-5(1eEx+^wr1$EA;r{I)-*$fW^G^Oa;X$-&OC~qR<4v|;%u{b7_|1^v^ zo3H*bBa@2)w{ag&bhf>!aXkC{V#}z!+N!17q5PUR;>2-qvBI9P z%T#@R(_V@gc@*xH%~~5|^ZBN$p$RP#&YK<|&QK_DH4uSkb+kDpw3rWsut z^kX$Uy3ua~D>tuy;+H98Tn|qcj|(}!HsA@n^k~ARn2W3MTlL14yWHC8IUnE#dGmuNfQlFLalM^ByS}p#t ztFEaa87iZlP4Cy3_ym?GCoA@R2Cd;xjxUf2upm!~;cwSccyCYTSBd^)ra0!??$?=I z0%_$dR(wK4o((DD&Z1hi1hRj8#p($nwn&<#cukzuPnJD0s5L@3GvIy&E<9G?F?vig zR$>*EbFxn;X4tkBBR=yYB^XxQ-P9?9t6W?GR4GU@+DXUW_Laj1mHI9OY%U4A@6!7- zC6!7zOrDC#lqjzuS)6%uV3$uAQ%8DXB7_#4oVZMn8#?0A94iC$s?DOrI7vOkP4!Qe zC$!{_%}GiqlqTRsDGvNEn7Q<;a~~(;=80x{USeQ|+&N8sjM3zOHrvi$G@b#0pW0{| zifVuFDO;qbKTgIgWTt1nME#VmJ>`3i&(s_T6=hP)R+LH4hhbEU6q$ z3~wtZ4I)YW*D@1ckPmB!mPjkVXlz;;;P*44Ahsvyvg;1?A$kxZST{`tP$X`d;TwF> zU4NifKcL)lDMqA4tLm>N!4K540}A>!J{x6uE!}~w^QA1>&Nq~M`iMVWafSRRMaQS< zBFQl{tqNxtB;Tl?6zE9w8HYF*pg4TcYfbccC$n6jhsD25B4ZI{%_u6 z)(BH`Qhk*h4ofOpqg6oLgg42@N4R{T&fk!M-=3(~Ecx|8QV(S&Z#dm=S;f|s@|5Fw zK|$L3*g;He0_tbO>d&MsX%-u(#up#bkz`Kn%o+n`uS>7L!_rVv$bE37y*KWmZm4Vq zn7!tTfj!h{v8}mzSc}W5vSDT~&kED$?!lwd)NOYu^x==JL}Nl}f3f5Wqe~UzASU@R zbv~1C?)@Is`o-9QKCSH4qvy<1W1GdrmU9W{Sx)GDrvEb$#|a0w?BNqO8o^p@c50F= z@r*-vUULfR%;+?8swnf5y;y<>t4Gk8SHJ^iPPx~)mfG8+(&J*IwiqHazGWG9YJ6f! z1sSMtPQ#g_0)0d)bMth)ejB!4`a6Ik6{0EXTOUG%nX&@O%E0RO%2Y@sZE+-3VFq-5 zuwaH2@(N<`U*^e>)ZX7u9#CcxR5D@pm8SW-9^2c^w(@0Wp+>EUIGtHSW=1ze} zISuSz>ld!|vnS2Db_z(?NjX#~`I}~wiuTx4QfIz2F1*jg%)4Q}Y8nRjEq#_@Ek*F5 z=GX-HYjG+`$_r`TbNONhJAZK$@lrCrAwq_QaQ%o$TNZ3|j7xa`i)^#4=y0=moRcPA zP~TvdZtf@FZ!sG86A2p*?sP%v#}1kpdt|>xZecjaF%Dnazh@++UdSu4MUI9?Q-g$y ziQHG`hH&-v%y@n*15Pr(vi}DAzTnl+mWY2G!FEjB=VYZ14wL}%PO4ai<`;63;_1;k z+O!yIs8?O#1?^Wx0YZC|JBGJPswUaFus@f%;TX`op}I*EsWO;YGfZYt;whk_2Xac; zV)sZy$+$~JHksJ00j2j6Y#t1cq51=Upi(y8Sx^O|S+uP3J)!PBV-`fxQ}Zj@yXD$e zt-aG|)e}nn$DSL!(%B(Y(ivV~$CTs}-Ai>%`(aGG2arLqQKQ4iRkWWKxSCTAUNAN^ z-E#%rp~=hlM?2#1ud}G%N$405&lM&rT~p@vCMpa_=!J$$E}CaJH<6#Y)wu*TmY`YK ze|1r(RO)MKbqMXy4<}(E#!7?WM=5r@+?LEb0vY`>DNAQrpKpdA_EXng46BT;IxA1> z62INQF|)@h{FqjANSmlxBR*9@M?{v9h7@)eOI~PrT-Q!cajC{(F4-tnys5*XJn8YxRZZ&%W${<5GATJ>Lp3YPdvv*OG`ROGb1#_D5ca1+9?1X^$!uV8{YZS!iqSX zL8*^P_4J7aewu&4^2HOlGFXg&d*woAMKEAg5kn8WyQYA%hr;?Z=P+pqS?u?QI#IP~ zK{~bxik*`Uck#CtT@JyC_`f@Zao*5$5^`q7RWhpOTH-nV)n0J%t)yJzk7}+!OOSv2 z)8Z+4`1RZxJm=`ds-AJqG$oe(gN9F~X*f|4#>pZgEK|#)tbIC@Jz|tzdjk0h2d5v5 zKT((#ilw(&bRy(7TjHmJ)hYDn8QX6J+b z?Q`UEuvLLq+OZ}pIVfRyJp8=D!5!LRaq`hEt8J0>Oj=;3{ zuE7vo+3r#pSHuWo^D_}`h{ z$TNNfcs|0BeZ{cCti$q2Q*$A1WrF%NFYSR~nIw@pm-9m7{7E%>Bt5{y z?k7)?rlID?1k9F#>WCBqw$6X#hRa>8~vd2!$2<+QFBMkF!v zQzOEOcCNfN4L|1^zJDHBHkX2>rT*joQKSA%avV>UP1qcU3SQHvi}pEmjo7uPF~L#VE? zx5PJ6gb{>Aw#6%%6YNwC93{Zvz^&QE_`HG>Iy)kRqeNBhmK-g1t$e}y41Ps#yNj6V zl)lq0M&eBxd`5%h-2C$tM_G{SJI=fdKDvhKVzmsJOGC@zDU)o{*zrSJ5klH1S5_f` zrxsmD7mqekME(s$DXBZ9xH}tcNkJ86v!qUA45N3t&rrr)V!FOuB7_C~EJLhf?^7?G zM|rmtPdGcZz@KE|Dw#oEua(l?6Ug@;~1s_c%Wh9$}=4Fr_m>fk38$a?Y9!Ol2USO%Dd5*5%g())kGKc zJVMN^^mq5oUi^z_Pfva#=5)*(d8-;{1`z7E{daAxzWXy$CB!>%QHM$M^=bRRFD|x$ z5Ihb<0Sk+vT`9$rUXrH5LeAXB?m{=^yf`2634?J6s03XvfS6cfdN9`W_@V+5SP#%| zcKyyVnaU)D1Y{m^PD!z5>z@&#tEWI-$Ui%Q$XJbJD7@p3%N4h72}RK5*~wL%av+jF zP^B;{7M22fQYLuU!8cKXj902T2G8`oe4I`9y+D%3tQazd6!N$Q4r*{_`wqGKdKvbx z_Oh2kX+el)K9|`)t|{Y2Rlz&4WMQ|W2jk>hV)n`H^&h8HjYo1-FnKLC#$121LB1!p zg*+Z~ZyJ6|oUUI`7zZ$KNlq5#e^|7-BX5MRB7ZiHlcD@|H!unLPLsEimq-v)P}NfJj&C+Bl{LqEg4+9bbkGwHNX*XMS)c?Q~Lu10piBISjJ*zuklXrKre^s?#p>o3N5 z7dnA|My@6JYlc&~by+Zp2Nx{1wDP?>&p7*?3Im^<2iYmki-I@&yn^4v3+*)Ls4&%w zI+zHuvG&_{{F6NXSNq2gHWk)Vk5$=m^4JWrAY;103N`9E5mk)G z3|3WJZz*GYQUh}gC5{3d(lO`L7R;QwH^_)Nc3N5k?`}m(%6eeGVYZ;!%gv{ z!#LL3L(zAu#{LYlmgoY&#W!T$307NuR!{@o(;zE)OG!Bo>d%z$eGV#c15O$T0340ydrs6aLyl^U9YKb*?TK!xCHVH`{a6PaSt< z)@|R2C9D_aFm4M+`xKhC)e3osihA?#Y5LTE6?SS-zdq^})BCmDyBF>+X7*YA**9)_ z%cAcmzG_8^CeQ2qYYC{rDWAvC`dh&87@;L@^M1-JB~12%z|iaWE-D)vb!`tjhkQK+ zIIP~6iW#bIgx1^NZiHqdtHocypTvG^Fq3I9>cvvA89NtwH@)lms7;V!9{aK-;GJKZ zeQi)UgZb^*Vl+?nLQL3F==rRA@skfo6G?Y!YtEH;x$)9g*8_3_zCeT9=e&#PRCH?5w?oY%2>R86Chbko2hvskoFLd zDvi;;CaB&bKb0*(HpOhxlJfz>*=()!!w2|SACeT7g93XRj9I++NYtz^#*tc`C?!h9 zapI`)Ckv%cjeU60L7AEwSa}QN@jc{CgRg0BOYjne2sd>|?i$?P;A!TDG>ZABSnyN- zoWsV-R`ASs;O0fVxZqalPU3pjkML>_g>bqAY32M8e@%(O@9fssU0sjVGR^(BsSoVc zYsrcZ!&NSm-|TM8C?`LnTrS-EqIAf3$2c#1Y_A*Z__;gJlk%As{{e_Zl1-?B7;xpl z=ob$L@0;@}hVmzaC3%=K20j2oMXtQ=AEz2imr+pNP6$8-a@bMXxxmwe8 z%B<^co?ukFPsxhAinCx5t@;~H@J$2vfrLZNptPSnTgQy;v(Vzk%$Bqw!GhN9Eh=(j zp2#o$M$`v%fVg zs5YYlx+2c9B?`F~Tt0nzL4~QuQvMEpeN1_6U+e|%y0#O%8xl508W*M3<5$&ch={*t zdKF={nqMq8$LlptJkhn&$Nk*T{q3l|Wn`W}@w7_l!hfeHop*mYsP#8K1+6yo1!C3uRU}g(@tjA@?WUr)G?X14AVbaCTKyV++6*Cs?vF~bI7r5scQz1iIJQD)5E9Hq#Bqi#_#4?`y0$6+>jI_{12 z!CF*%4W#P|X{2k~e1jdR+D5JEY9NW&)tqWIg>pMLsBvLo(I~s!DPGZU@ccj%qXD*9G6b{RR($QNq3eiEEse?#hZarEvE+Bk zoHCg-N{#IQ4bt1=I%=`!?+Poc?z00w@i zo@Z+R)(U4hwo3d2H{FHRRdBgndxdhaoJEznS>;UmUpOL1O^ODQxrYwAHHIJ2?y6k7 zpaNv?U(By-X#u^b)Qt?D^^~e-Ve^%4Z=YA~90``}@A~{~q`rC9l`IlHHZo^k>RPK_ z3bg?bk;X9@=O5)%TUZY91$c#1Q#2$c&SmVGchFlK z`IVTh^z-*4i|WdQ*=)4Q)be+_LZ94iS;`as;}Vfyi$p)k>24a@T7!yk{+^PwD_e&^ zJ95NU2P3P@1=`k*Hse=|JC0;sJ_fA6{x^cMS(~mGP^egY6R2vnrSBKI6-=|mc6WYE zK(;w#Xlgsgg3tUXreZuW?3iPE8@u=DNUxIw9(FucR4WD(O1qqRVR7-mCaM&-UNG-1 znhZ8SotIkPdorc<$JV+#z84od7@IR15MT6(e0qK=vV}~l&{9HGQ-XEbSQ{FHyRgAv z)$311xlfKaps7`&^E}N|qBQN{X-PV7aZ{hxOvSQi$X4V+E?vubv&=hfqJ@bI>&|(y z@MXN=7bHrwcz8l@YFO8+M4{d}%=MV&tKAYl$5xK5uD0_hc$zcso3kPctya^|Y~)JG^_(D4d(;c4h<%Vr!$|nn zDKkbtsh01PLD{sGH&QNbu2 z-wa0u3)4uae#$iX*CW@mcX#@U!scXN{HpSj(5EnMoAG?pdcw5ntda{Tp2Cqr1-+|D zntvm?4`>y7`{oC>o+>eGu7ScZ0AevI)mp{_F=@i?k^l={^G|f z?t1{8X~>9%0C^^R_YZMyge-}=%5v0KHXoIIgcmGsArMnWNU)1c)Do z@28ew9yt%qN?@_D(x^Cj?wg=QrM=u1Td|m4)4rVFkPbIuusr5QsWoQ%CV+2}@ylkN zpN|(AEvjPUk_-gNCgh8&)7`772%aWRt4?p}Xw)7vmQzILI1ujvqB$GhoWhGv$&s{0 zHCFWunpWl%VsYA;P&LoZ#&)*)hJ#!tB4nbpjl2}q7nQn=Qe9u&Pwh5Mu2fTu&gB&t zt?M(>Z#f^C$@E@u5cHHa8Bit2hsS=i(&{jpYBk%4PMnSWUMwL=GzlUb_$t6il^_|u z%O{rMmW0pA$ltLS>yk+OwwdO&l;o#-4W}F@Ja^b9py-nS4)LaGK-T z4FEN_PSTFx&@)DI;t9mkE}m2$Q7i~NTydX%bNT`@Rsfn{nXH|cGYGz62s1jI+IL%^ zJNL&n;)?@wFKTcv+#3II%>vKU6iu4c8Co{*r(S5MSyq39%ddC}v3zTOTZ7pSozt3Te5F(*=tavD10R~%9MDk}&oBEnxWLOa?# zSa>rKyuiJnmTGbG{Zb&vb*y}229ZmFMIj6giTd(D8fP!cEu0?HGFdg5E7fh%yNpR##;G>k2l9RpS z_4wQIrn(SNZ)Rn`r>-q(av+x@Tm$bRXKRBjceynGh7lY;(sYcJ^siU!gF44|%@ZeN z)LD$>%f?OzvZCrr8MW`|8qL$d`&GmJ#PN+K{s#80Ox7E1`grBs11IlbC$AnlFq9M~ z6-j#WzTfnYeQS(<)do@}{akWQgGZR9GTpW24}j^Qv)*7!Jc7`5Rw(bVEM|%{a^U)(SHD8)C>#u6fv|;o-~_Pfr3C zydS?!krK*SyWJQ$UQg7|t#ud|5MtllHD0BN&p`0OGPRwbZ4upA{#g=g6-T4xv~ZH? z8?@dM(KEs7%dF)E`?ZC~D*o}q!AwPWtn!v#U`NC4u&JTwh3OxPL519fz=X}rmEJ6#buruHKvX)Cuxo8a2jDO={i=1Tp~N;V=c@e5%;Y2>cDZg)YTkU z^_$!CBjN9*L%EHSH1s*kAIR5Qb|kwtnki;6T^fUvML8TD7yF(1E98vj{wl?6i?Lck z1Rhr8+9Z~0zP`f`H1yi-GfO*jvUt{q+D!Pwm;FKY+HNGhDOE$+|Flmd6RUH#({Rg8 zHlz)L2gZZ{Y06Zk9@5+pJ}5QNZ_>dx?~_5gL-C4iG&IG+`oGyE3aZH9ce1wHX83YA;`YzZs5loCN{r zUvIrv&h-Md!t~Ajf^UD?X)uXyBvWK&#%X^${2&4rFoy5C_?pYKxL7?TWA*`;K;v}q z@O8|guJyW8jVom{f*m-$Qc`tzR9_)0MDE`oy|HrOBPw}e6opNg zio8N@3ZHGYefDmw!Wl)It*QsL=&)CSGW)wtN*TpY0V(%M^kO8&VO2X~PU z(v*G0vUyjd|@O zgl5*aQlv~aL4=V)5qbRlR=Yd%k@B$c&u%QgP`4QqfC{GEyP7!)vQ(uwM$QO1>B1^G zCM&FTIsO;M5IOInp?_QT!tkef6L=E^?HQq1$_CC1mzab`Oh8QYGzg~OPc>ZzV}YjS z74gQbruW2pyKBs)w=)G>5|wuvdEWikxzU9QJq2S+l## z7l=cSHnj59lB z+|==d4H+=S+Q|_>kZ3~!KtY2BlMqx0fMCIf2ipKh!tkL)iwhYZEI2V?M2!sxCXqxU zA%KCBl58Wnl3+xV8xQ`IIg@5hn>S^`)47vpPoF=51{Deq(MOF(8)7tAkttG&N1 zYUEf|@JNhkq2AT&@Mw~f61o2ZAWLXUakXGO8V+}F>l66HV1e6nRszjvXRGb4LiAV#?4D-jjo(8WycPasI|3? zdqTG0d8yf*L?@E^6>*v6u;2yE-X5#I`Jx(MZyFvaDZLs3P>8sZHw zg(4b>I}-34FhsUU>`^FG9&7Hy2WOjas_CK<=_Mqki!ZCMP@<|ou@C}oq>>)mkw+i7 z^oboIUj#GE6d?2=mryPNE$(WcDQIwWyr(GRudP~JzHbaE^UpKORL28N{%bT63zFzjK(6t z+?mjgmK|1zsMb?6oy*bBhsa>3Ub)sYvNW$?>gu01zFKiCLkBLn;Dh}WaI1`EOV%ib zh(Kjxx!5V{#9I0g9>d5nBA}6&ICeld4KRj_ zEUQx(CnD>vq#7EF=bnEKn;m9Y0l^mA_;Tsq%aUM&6=s}%`Z!wONnQ$yXA{h$}RdSoOv1>c}dE)alm6w;CFLP!b9yB87u#?@2EBsN5A5&5x zuc;{^{E3{0Y9pXny=g;MlAl4M#1%rK&yccXhD#h)vxS_^j-1?(3w4vIC`rmOtpB`B zhuTP%h@?beh)UtPN*OjFQ<)MKu*OE;btHo$XNhLli?!=0m@HZz|&fCI3Ra;+^WO zjjJp5s-2Wby4mpUi5#;Tz-}WcY+6V>TglNgC&dsh>TG^>1?F8zq{M#4C?&^?4Hopc!#djVOz9?yDripT4Ys#~fk%%X9EnyDwg8#ad`$DV2#fgM< z+~t_Xt8?z7vQOCAeztKg@NKTTSM5PgSz*%EjXo)i9l#Xt{ zT``e~lUJ)K)5B3@-oEH-NB7tcLfTvLO|bkKMjWtJRDq&exHCzbUb9bR7S}~5d}bA< z87`4<;YtXjnxiyA#dy-WP90U_r~ekwh}PNO(YAvUi>I8a ztyenhiu$>n?3nvm=mv^anG(ZzzB^0;*$=(zZST4C@G2M*OLBKStN0Zg;1NZ)5?gE0 zkHK-rQ&S#78ctM!?}QC*wM^FxW5F}Ei#}YzcE%O8ah`nWDmcQRjWO651z8#8M^w3F zk$1uVz?s1MS`i9suJMgO3OoOj%#0n$%Ix&GHRcU^8C4E6w8F$At0DGsl{vX0o&4!3 zk2*XU;?Um4Dvc#2Fm`LG5w3TgPG5%y8wi>fYQ}Qa4FnI{RqFO>UE ze&MOT{Iz-fF_2BN_S2h(^IytZSXNR?`uZG{voGY=Bj4=XZ%8+?YHD*#@B0vO9v-9n zvTiVh%n#daI?5-d+kHjieHRfpQt4%Ht#9+MGed@}ou->mLYiFfEc~0Vy+D@E5fF0f zq_7TN`?bV;cz~dqmBf+Nr^KMLg68n=*f@{;=m&NB!ovzjFbWX$m@kHWN45GzKNOJN zmgfQg&rSb8U}_>PR?cR;iX_iVwj=NyqGNclK=cnDV&V7>ikx^TPX2-fk8n$p@HM=KO_ritQqbw9 za966(BTnHt0!WH5B_hSc zrmsjGsG9CB5IM0EqvzoCLnUA-vB1NvfCCj%aVIK>|^^ zsAf_}iz31rPfr}ju`wFVgB%EBuu4+G(H+^aC=_EX_{QQA1B~uZ1MzX?jv`8UCF0rk`2|)P(Z6z*3Ees@*;JjE|v%WM3DNxA`NE`Br)<5 zMdGL81!I(?A_Y<m!uIa2+;b9(kS2Qd9di4qK%CIcCsi_ zXCne;MMm)y-|{QVaXb(M&{ohZX|k;f!jgJOHckd5j_@px;%u_%aa!Uw4iheq5_MWD zE)9Ym9S<>8jPC-CT|Ck+Rnjgv!m1qT(kkgOosv@UO)_xhW0Vmu;cAI0g4ar_EZdJK zBd;L$(u3rvH9@ilqau)ALJaweHyg4UaWj9kX99n5Aa4^XSR-88$S&^@AK8Hs!~`L+ zV_zC^AS(tjHb`gEuMzjG7Uu@`urQo@ioe8j8M6mdO!2SyM(~z%5XmulOb82cLp;Y* z;3S3_5Fu*nsYz0XA^=S~=kwP3(>isgZf@pCP{up-^Rck(U=V=_yff`X;-j|0EwETF zLM61j#zrKS$2UF&x&qHr6k<0$l*l><1hP=DW}`HWZct2)Yxs-wAj3l)@viE@{&Mm+ z3$nKg9~ujGWn z2vllnmn_pW^NIXg(v7CVMzXYmRH8i~gGu!CayVkT=u}D#i5Z%pjiRrLplV#0w5gsG zMiYZJBebZD=ifFaGD1Q|+AC{tav^*ut<*_!VsKNnZBbut=Oh6UUZzA2h8*E>)TCB|EY==4=O?f>vM`otiDzZa_Gu>uX_3}Iv}|jK=WO9NZsitesda39 zwrtOq8RvFyxr7ki7H`8AE-IF87sqZhR(;I2Gahzr3%6$v7jflwDB!kl4L4*VcW*IU zmvPafa#8kjGZ%BagmYgOa!HmABiD3IS7&$DaV~Ul(^rWNvzwrK+|b$Tb?is&+@0N2|6G(x?r#rCgO4UjqUFI~|T23;+NC literal 0 HcmV?d00001 diff --git a/bin/reportlab/tools/pythonpoint/pythonpoint.dtd b/bin/reportlab/tools/pythonpoint/pythonpoint.dtd new file mode 100644 index 00000000000..ec876f68387 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/pythonpoint.dtd @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/reportlab/tools/pythonpoint/pythonpoint.py b/bin/reportlab/tools/pythonpoint/pythonpoint.py new file mode 100644 index 00000000000..c5e823de04d --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/pythonpoint.py @@ -0,0 +1,1124 @@ +#!/usr/bin/env python + +""" +This is PythonPoint! + +The idea is a simple markup languages for describing presentation +slides, and other documents which run page by page. I expect most +of it will be reusable in other page layout stuff. + +Look at the sample near the top, which shows how the presentation +should be coded up. + +The parser, which is in a separate module to allow for multiple +parsers, turns the XML sample into an object tree. There is a +simple class hierarchy of items, the inner levels of which create +flowable objects to go in the frames. These know how to draw +themselves. + +The currently available 'Presentation Objects' are: + + The main hierarchy... + PPPresentation + PPSection + PPSlide + PPFrame + + PPAuthor, PPTitle and PPSubject are optional + + Things to flow within frames... + PPPara - flowing text + PPPreformatted - text with line breaks and tabs, for code.. + PPImage + PPTable - bulk formatted tabular data + PPSpacer + + Things to draw directly on the page... + PPRect + PPRoundRect + PPDrawingElement - user base class for graphics + PPLine + PPEllipse + +Features added by H. Turgut Uyar +- TrueType support (actually, just an import in the style file); + this also enables the use of Unicode symbols +- para, image, table, line, rectangle, roundrect, ellipse, polygon + and string elements can now have effect attributes + (careful: new slide for each effect!) +- added printout mode (no new slides for effects, see item above) +- added a second-level bullet: Bullet2 +- small bugfixes in handleHiddenSlides: + corrected the outlineEntry of included hidden slide + and made sure to include the last slide even if hidden + +Recently added features are: + +- file globbing +- package structure +- named colors throughout (using names from reportlab/lib/colors.py) +- handout mode with arbitrary number of columns per page +- stripped off pages hidden in the outline tree (hackish) +- new tag for speaker notes (paragraphs only) +- new tag for syntax-colorized Python code +- reformatted pythonpoint.xml and monterey.xml demos +- written/extended DTD +- arbitrary font support +- print proper speaker notes (TODO) +- fix bug with partially hidden graphics (TODO) +- save in combined presentation/handout mode (TODO) +- add pyRXP support (TODO) +""" + +import os, sys, imp, string, pprint, getopt, glob + +from reportlab import rl_config +from reportlab.lib import styles +from reportlab.lib import colors +from reportlab.lib.units import cm +from reportlab.lib.utils import getStringIO +from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfgen import canvas +from reportlab.platypus.doctemplate import SimpleDocTemplate +from reportlab.platypus.flowables import Flowable +from reportlab.platypus.xpreformatted import PythonPreformatted +from reportlab.platypus import Preformatted, Paragraph, Frame, \ + Image, Table, TableStyle, Spacer + + +USAGE_MESSAGE = """\ +PythonPoint - a tool for making presentations in PDF. + +Usage: + pythonpoint.py [options] file1.xml [file2.xml [...]] + + where options can be any of these: + + -h / --help prints this message + -n / --notes leave room for comments + -v / --verbose verbose mode + -s / --silent silent mode (NO output) + --handout produce handout document + --printout produce printout document + --cols specify number of columns + on handout pages (default: 2) + +To create the PythonPoint user guide, do: + pythonpoint.py pythonpoint.xml +""" + + +##################################################################### +# This should probably go into reportlab/lib/fonts.py... +##################################################################### + +class FontNameNotFoundError(Exception): + pass + + +class FontFilesNotFoundError(Exception): + pass + + +##def findFontName(path): +## "Extract a Type-1 font name from an AFM file." +## +## f = open(path) +## +## found = 0 +## while not found: +## line = f.readline()[:-1] +## if not found and line[:16] == 'StartCharMetrics': +## raise FontNameNotFoundError, path +## if line[:8] == 'FontName': +## fontName = line[9:] +## found = 1 +## +## return fontName +## +## +##def locateFilesForFontWithName(name): +## "Search known paths for AFM/PFB files describing T1 font with given name." +## +## join = os.path.join +## splitext = os.path.splitext +## +## afmFile = None +## pfbFile = None +## +## found = 0 +## while not found: +## for p in rl_config.T1SearchPath: +## afmFiles = glob.glob(join(p, '*.[aA][fF][mM]')) +## for f in afmFiles: +## T1name = findFontName(f) +## if T1name == name: +## afmFile = f +## found = 1 +## break +## if afmFile: +## break +## break +## +## if afmFile: +## pfbFile = glob.glob(join(splitext(afmFile)[0] + '.[pP][fF][bB]'))[0] +## +## return afmFile, pfbFile +## +## +##def registerFont(name): +## "Register Type-1 font for future use." +## +## rl_config.warnOnMissingFontGlyphs = 0 +## rl_config.T1SearchPath.append(r'C:\Programme\Python21\reportlab\test') +## +## afmFile, pfbFile = locateFilesForFontWithName(name) +## if not afmFile and not pfbFile: +## raise FontFilesNotFoundError +## +## T1face = pdfmetrics.EmbeddedType1Face(afmFile, pfbFile) +## T1faceName = name +## pdfmetrics.registerTypeFace(T1face) +## T1font = pdfmetrics.Font(name, T1faceName, 'WinAnsiEncoding') +## pdfmetrics.registerFont(T1font) + + +def registerFont0(sourceFile, name, path): + "Register Type-1 font for future use, simple version." + + rl_config.warnOnMissingFontGlyphs = 0 + + p = os.path.join(os.path.dirname(sourceFile), path) + afmFiles = glob.glob(p + '.[aA][fF][mM]') + pfbFiles = glob.glob(p + '.[pP][fF][bB]') + assert len(afmFiles) == len(pfbFiles) == 1, FontFilesNotFoundError + + T1face = pdfmetrics.EmbeddedType1Face(afmFiles[0], pfbFiles[0]) + T1faceName = name + pdfmetrics.registerTypeFace(T1face) + T1font = pdfmetrics.Font(name, T1faceName, 'WinAnsiEncoding') + pdfmetrics.registerFont(T1font) + +##################################################################### + + +def checkColor(col): + "Converts a color name to an RGB tuple, if possible." + + if type(col) == type('') and col in dir(colors): + col = getattr(colors, col) + col = (col.red, col.green, col.blue) + + return col + + +def handleHiddenSlides(slides): + """Filters slides from a list of slides. + + In a sequence of hidden slides all but the last one are + removed. Also, the slide before the sequence of hidden + ones is removed. + + This assumes to leave only those slides in the handout + that also appear in the outline, hoping to reduce se- + quences where each new slide only adds one new line + to a list of items... + """ + + itd = indicesToDelete = map(lambda s:s.outlineEntry == None, slides) + + for i in range(len(itd)-1): + if itd[i] == 1: + if itd[i+1] == 0: + itd[i] = 0 + if i > 0 and itd[i-1] == 0: + itd[i-1] = 1 + + itd[len(itd)-1] = 0 + + for i in range(len(itd)): + if slides[i].outlineEntry: + curOutlineEntry = slides[i].outlineEntry + if itd[i] == 1: + slides[i].delete = 1 + else: + slides[i].outlineEntry = curOutlineEntry + slides[i].delete = 0 + + slides = filter(lambda s:s.delete == 0, slides) + + return slides + + +def makeSlideTable(slides, pageSize, docWidth, numCols): + """Returns a table containing a collection of SlideWrapper flowables. + """ + + slides = handleHiddenSlides(slides) + + # Set table style. + tabStyle = TableStyle( + [('GRID', (0,0), (-1,-1), 0.25, colors.black), + ('ALIGN', (0,0), (-1,-1), 'CENTRE') + ]) + + # Build table content. + width = docWidth/numCols + height = width * pageSize[1]/pageSize[0] + matrix = [] + row = [] + for slide in slides: + sw = SlideWrapper(width, height, slide, pageSize) + if (len(row)) < numCols: + row.append(sw) + else: + matrix.append(row) + row = [] + row.append(sw) + if len(row) > 0: + for i in range(numCols-len(row)): + row.append('') + matrix.append(row) + + # Make Table flowable. + t = Table(matrix, + [width + 5]*len(matrix[0]), + [height + 5]*len(matrix)) + t.setStyle(tabStyle) + + return t + + +class SlideWrapper(Flowable): + """A Flowable wrapping a PPSlide object. + """ + + def __init__(self, width, height, slide, pageSize): + Flowable.__init__(self) + self.width = width + self.height = height + self.slide = slide + self.pageSize = pageSize + + + def __repr__(self): + return "SlideWrapper(w=%s, h=%s)" % (self.width, self.height) + + + def draw(self): + "Draw the slide in our relative coordinate system." + + slide = self.slide + pageSize = self.pageSize + canv = self.canv + + canv.saveState() + canv.scale(self.width/pageSize[0], self.height/pageSize[1]) + slide.effectName = None + slide.drawOn(self.canv) + canv.restoreState() + + +class PPPresentation: + def __init__(self): + self.sourceFilename = None + self.filename = None + self.outDir = None + self.description = None + self.title = None + self.author = None + self.subject = None + self.notes = 0 # different printing mode + self.handout = 0 # prints many slides per page + self.printout = 0 # remove hidden slides + self.cols = 0 # columns per handout page + self.slides = [] + self.effectName = None + self.showOutline = 1 #should it be displayed when opening? + self.compression = rl_config.pageCompression + self.pageDuration = None + #assume landscape + self.pageWidth = rl_config.defaultPageSize[1] + self.pageHeight = rl_config.defaultPageSize[0] + self.verbose = rl_config.verbose + + + def saveAsPresentation(self): + """Write the PDF document, one slide per page.""" + if self.verbose: + print 'saving presentation...' + pageSize = (self.pageWidth, self.pageHeight) + if self.sourceFilename: + filename = os.path.splitext(self.sourceFilename)[0] + '.pdf' + if self.outDir: filename = os.path.join(self.outDir,os.path.basename(filename)) + if self.verbose: + print filename + #canv = canvas.Canvas(filename, pagesize = pageSize) + outfile = getStringIO() + if self.notes: + #translate the page from landscape to portrait + pageSize= pageSize[1], pageSize[0] + canv = canvas.Canvas(outfile, pagesize = pageSize) + canv.setPageCompression(self.compression) + canv.setPageDuration(self.pageDuration) + if self.title: + canv.setTitle(self.title) + if self.author: + canv.setAuthor(self.author) + if self.subject: + canv.setSubject(self.subject) + + slideNo = 0 + for slide in self.slides: + #need diagnostic output if something wrong with XML + slideNo = slideNo + 1 + if self.verbose: + print 'doing slide %d, id = %s' % (slideNo, slide.id) + if self.notes: + #frame and shift the slide + #canv.scale(0.67, 0.67) + scale_amt = (min(pageSize)/float(max(pageSize)))*.95 + #canv.translate(self.pageWidth / 6.0, self.pageHeight / 3.0) + #canv.translate(self.pageWidth / 2.0, .025*self.pageHeight) + canv.translate(.025*self.pageHeight, (self.pageWidth/2.0) + 5) + #canv.rotate(90) + canv.scale(scale_amt, scale_amt) + canv.rect(0,0,self.pageWidth, self.pageHeight) + slide.drawOn(canv) + canv.showPage() + + #ensure outline visible by default + if self.showOutline: + canv.showOutline() + + canv.save() + return self.savetofile(outfile, filename) + + + def saveAsHandout(self): + """Write the PDF document, multiple slides per page.""" + + styleSheet = getSampleStyleSheet() + h1 = styleSheet['Heading1'] + bt = styleSheet['BodyText'] + + if self.sourceFilename : + filename = os.path.splitext(self.sourceFilename)[0] + '.pdf' + + outfile = getStringIO() + doc = SimpleDocTemplate(outfile, pagesize=rl_config.defaultPageSize, showBoundary=0) + doc.leftMargin = 1*cm + doc.rightMargin = 1*cm + doc.topMargin = 2*cm + doc.bottomMargin = 2*cm + multiPageWidth = rl_config.defaultPageSize[0] - doc.leftMargin - doc.rightMargin - 50 + + story = [] + orgFullPageSize = (self.pageWidth, self.pageHeight) + t = makeSlideTable(self.slides, orgFullPageSize, multiPageWidth, self.cols) + story.append(t) + +## #ensure outline visible by default +## if self.showOutline: +## doc.canv.showOutline() + + doc.build(story) + return self.savetofile(outfile, filename) + + def savetofile(self, pseudofile, filename): + """Save the pseudo file to disk and return its content as a + string of text.""" + pseudofile.flush() + content = pseudofile.getvalue() + pseudofile.close() + if filename : + outf = open(filename, "wb") + outf.write(content) + outf.close() + return content + + + + def save(self): + "Save the PDF document." + + if self.handout: + return self.saveAsHandout() + else: + return self.saveAsPresentation() + + +#class PPSection: +# """A section can hold graphics which will be drawn on all +# pages within it, before frames and other content are done. +# In other words, a background template.""" +# def __init__(self, name): +# self.name = name +# self.graphics = [] +# +# def drawOn(self, canv): +# for graphic in self.graphics: +### graphic.drawOn(canv) +# +# name = str(hash(graphic)) +# internalname = canv._doc.hasForm(name) +# +# canv.saveState() +# if not internalname: +# canv.beginForm(name) +# graphic.drawOn(canv) +# canv.endForm() +# canv.doForm(name) +# else: +# canv.doForm(name) +# canv.restoreState() + + +definedForms = {} + +class PPSection: + """A section can hold graphics which will be drawn on all + pages within it, before frames and other content are done. + In other words, a background template.""" + + def __init__(self, name): + self.name = name + self.graphics = [] + + def drawOn(self, canv): + for graphic in self.graphics: + graphic.drawOn(canv) + continue + name = str(hash(graphic)) + #internalname = canv._doc.hasForm(name) + if definedForms.has_key(name): + internalname = 1 + else: + internalname = None + definedForms[name] = 1 + if not internalname: + canv.beginForm(name) + canv.saveState() + graphic.drawOn(canv) + canv.restoreState() + canv.endForm() + canv.doForm(name) + else: + canv.doForm(name) + + +class PPNotes: + def __init__(self): + self.content = [] + + def drawOn(self, canv): + print self.content + + +class PPSlide: + def __init__(self): + self.id = None + self.title = None + self.outlineEntry = None + self.outlineLevel = 0 # can be higher for sub-headings + self.effectName = None + self.effectDirection = 0 + self.effectDimension = 'H' + self.effectMotion = 'I' + self.effectDuration = 1 + self.frames = [] + self.notes = [] + self.graphics = [] + self.section = None + + def drawOn(self, canv): + if self.effectName: + canv.setPageTransition( + effectname=self.effectName, + direction = self.effectDirection, + dimension = self.effectDimension, + motion = self.effectMotion, + duration = self.effectDuration + ) + + if self.outlineEntry: + #gets an outline automatically + self.showOutline = 1 + #put an outline entry in the left pane + tag = self.title + canv.bookmarkPage(tag) + canv.addOutlineEntry(tag, tag, self.outlineLevel) + + if self.section: + self.section.drawOn(canv) + + for graphic in self.graphics: + graphic.drawOn(canv) + + for frame in self.frames: + frame.drawOn(canv) + +## # Need to draw the notes *somewhere*... +## for note in self.notes: +## print note + + +class PPFrame: + def __init__(self, x, y, width, height): + self.x = x + self.y = y + self.width = width + self.height = height + self.content = [] + self.showBoundary = 0 + + def drawOn(self, canv): + #make a frame + frame = Frame( self.x, + self.y, + self.width, + self.height + ) + frame.showBoundary = self.showBoundary + + #build a story for the frame + story = [] + for thingy in self.content: + #ask it for any flowables + story.append(thingy.getFlowable()) + #draw it + frame.addFromList(story,canv) + + +class PPPara: + """This is a placeholder for a paragraph.""" + def __init__(self): + self.rawtext = '' + self.style = None + + def escapeAgain(self, text): + """The XML has been parsed once, so '>' became '>' + in rawtext. We need to escape this to get back to + something the Platypus parser can accept""" + pass + + def getFlowable(self): +## print 'rawText for para:' +## print repr(self.rawtext) + p = Paragraph( + self.rawtext, + getStyles()[self.style], + self.bulletText + ) + return p + + +class PPPreformattedText: + """Use this for source code, or stuff you do not want to wrap""" + def __init__(self): + self.rawtext = '' + self.style = None + + def getFlowable(self): + return Preformatted(self.rawtext, getStyles()[self.style]) + + +class PPPythonCode: + """Use this for colored Python source code""" + def __init__(self): + self.rawtext = '' + self.style = None + + def getFlowable(self): + return PythonPreformatted(self.rawtext, getStyles()[self.style]) + + +class PPImage: + """Flowing image within the text""" + def __init__(self): + self.filename = None + self.width = None + self.height = None + + def getFlowable(self): + return Image(self.filename, self.width, self.height) + + +class PPTable: + """Designed for bulk loading of data for use in presentations.""" + def __init__(self): + self.rawBlocks = [] #parser stuffs things in here... + self.fieldDelim = ',' #tag args can override + self.rowDelim = '\n' #tag args can override + self.data = None + self.style = None #tag args must specify + self.widths = None #tag args can override + self.heights = None #tag args can override + + def getFlowable(self): + self.parseData() + t = Table( + self.data, + self.widths, + self.heights) + if self.style: + t.setStyle(getStyles()[self.style]) + + return t + + def parseData(self): + """Try to make sense of the table data!""" + rawdata = string.strip(string.join(self.rawBlocks, '')) + lines = string.split(rawdata, self.rowDelim) + #clean up... + lines = map(string.strip, lines) + self.data = [] + for line in lines: + cells = string.split(line, self.fieldDelim) + self.data.append(cells) + + #get the width list if not given + if not self.widths: + self.widths = [None] * len(self.data[0]) + if not self.heights: + self.heights = [None] * len(self.data) + +## import pprint +## print 'table data:' +## print 'style=',self.style +## print 'widths=',self.widths +## print 'heights=',self.heights +## print 'fieldDelim=',repr(self.fieldDelim) +## print 'rowDelim=',repr(self.rowDelim) +## pprint.pprint(self.data) + + +class PPSpacer: + def __init__(self): + self.height = 24 #points + + def getFlowable(self): + return Spacer(72, self.height) + + + ############################################################# + # + # The following are things you can draw on a page directly. + # + ############################################################## + +##class PPDrawingElement: +## """Base class for something which you draw directly on the page.""" +## def drawOn(self, canv): +## raise "NotImplementedError", "Abstract base class!" + + +class PPFixedImage: + """You place this on the page, rather than flowing it""" + def __init__(self): + self.filename = None + self.x = 0 + self.y = 0 + self.width = None + self.height = None + + def drawOn(self, canv): + if self.filename: + x, y = self.x, self.y + w, h = self.width, self.height + canv.drawImage(self.filename, x, y, w, h) + + +class PPRectangle: + def __init__(self, x, y, width, height): + self.x = x + self.y = y + self.width = width + self.height = height + self.fillColor = None + self.strokeColor = (1,1,1) + self.lineWidth=0 + + def drawOn(self, canv): + canv.saveState() + canv.setLineWidth(self.lineWidth) + if self.fillColor: + r,g,b = checkColor(self.fillColor) + canv.setFillColorRGB(r,g,b) + if self.strokeColor: + r,g,b = checkColor(self.strokeColor) + canv.setStrokeColorRGB(r,g,b) + canv.rect(self.x, self.y, self.width, self.height, + stroke=(self.strokeColor<>None), + fill = (self.fillColor<>None) + ) + canv.restoreState() + + +class PPRoundRect: + def __init__(self, x, y, width, height, radius): + self.x = x + self.y = y + self.width = width + self.height = height + self.radius = radius + self.fillColor = None + self.strokeColor = (1,1,1) + self.lineWidth=0 + + def drawOn(self, canv): + canv.saveState() + canv.setLineWidth(self.lineWidth) + if self.fillColor: + r,g,b = checkColor(self.fillColor) + canv.setFillColorRGB(r,g,b) + if self.strokeColor: + r,g,b = checkColor(self.strokeColor) + canv.setStrokeColorRGB(r,g,b) + canv.roundRect(self.x, self.y, self.width, self.height, + self.radius, + stroke=(self.strokeColor<>None), + fill = (self.fillColor<>None) + ) + canv.restoreState() + + +class PPLine: + def __init__(self, x1, y1, x2, y2): + self.x1 = x1 + self.y1 = y1 + self.x2 = x2 + self.y2 = y2 + self.fillColor = None + self.strokeColor = (1,1,1) + self.lineWidth=0 + + def drawOn(self, canv): + canv.saveState() + canv.setLineWidth(self.lineWidth) + if self.strokeColor: + r,g,b = checkColor(self.strokeColor) + canv.setStrokeColorRGB(r,g,b) + canv.line(self.x1, self.y1, self.x2, self.y2) + canv.restoreState() + + +class PPEllipse: + def __init__(self, x1, y1, x2, y2): + self.x1 = x1 + self.y1 = y1 + self.x2 = x2 + self.y2 = y2 + self.fillColor = None + self.strokeColor = (1,1,1) + self.lineWidth=0 + + def drawOn(self, canv): + canv.saveState() + canv.setLineWidth(self.lineWidth) + if self.strokeColor: + r,g,b = checkColor(self.strokeColor) + canv.setStrokeColorRGB(r,g,b) + if self.fillColor: + r,g,b = checkColor(self.fillColor) + canv.setFillColorRGB(r,g,b) + canv.ellipse(self.x1, self.y1, self.x2, self.y2, + stroke=(self.strokeColor<>None), + fill = (self.fillColor<>None) + ) + canv.restoreState() + + +class PPPolygon: + def __init__(self, pointlist): + self.points = pointlist + self.fillColor = None + self.strokeColor = (1,1,1) + self.lineWidth=0 + + def drawOn(self, canv): + canv.saveState() + canv.setLineWidth(self.lineWidth) + if self.strokeColor: + r,g,b = checkColor(self.strokeColor) + canv.setStrokeColorRGB(r,g,b) + if self.fillColor: + r,g,b = checkColor(self.fillColor) + canv.setFillColorRGB(r,g,b) + + path = canv.beginPath() + (x,y) = self.points[0] + path.moveTo(x,y) + for (x,y) in self.points[1:]: + path.lineTo(x,y) + path.close() + canv.drawPath(path, + stroke=(self.strokeColor<>None), + fill=(self.fillColor<>None)) + canv.restoreState() + + +class PPString: + def __init__(self, x, y): + self.text = '' + self.x = x + self.y = y + self.align = TA_LEFT + self.font = 'Times-Roman' + self.size = 12 + self.color = (0,0,0) + self.hasInfo = 0 # these can have data substituted into them + + def normalizeText(self): + """It contains literal XML text typed over several lines. + We want to throw away + tabs, newlines and so on, and only accept embedded string + like '\n'""" + lines = string.split(self.text, '\n') + newtext = [] + for line in lines: + newtext.append(string.strip(line)) + #accept all the '\n' as newlines + + self.text = newtext + + def drawOn(self, canv): + # for a string in a section, this will be drawn several times; + # so any substitution into the text should be in a temporary + # variable + if self.hasInfo: + # provide a dictionary of stuff which might go into + # the string, so they can number pages, do headers + # etc. + info = {} + info['title'] = canv._doc.info.title + info['author'] = canv._doc.info.author + info['subject'] = canv._doc.info.subject + info['page'] = canv.getPageNumber() + drawText = self.text % info + else: + drawText = self.text + + if self.color is None: + return + lines = string.split(string.strip(drawText), '\\n') + canv.saveState() + + canv.setFont(self.font, self.size) + + r,g,b = checkColor(self.color) + canv.setFillColorRGB(r,g,b) + cur_y = self.y + for line in lines: + if self.align == TA_LEFT: + canv.drawString(self.x, cur_y, line) + elif self.align == TA_CENTER: + canv.drawCentredString(self.x, cur_y, line) + elif self.align == TA_RIGHT: + canv.drawRightString(self.x, cur_y, line) + cur_y = cur_y - 1.2*self.size + + canv.restoreState() + +class PPDrawing: + def __init__(self): + self.drawing = None + def getFlowable(self): + return self.drawing + +class PPFigure: + def __init__(self): + self.figure = None + def getFlowable(self): + return self.figure + +def getSampleStyleSheet(): + from reportlab.tools.pythonpoint.styles.standard import getParagraphStyles + return getParagraphStyles() + + +#make a singleton and a function to access it +_styles = None +def getStyles(): + global _styles + if not _styles: + _styles = getSampleStyleSheet() + return _styles + + +def setStyles(newStyleSheet): + global _styles + _styles = newStyleSheet + +_pyRXP_Parser = None +def validate(rawdata): + global _pyRXP_Parser + if not _pyRXP_Parser: + try: + import pyRXP + except ImportError: + return + from reportlab.lib.utils import open_and_read, _RL_DIR, rl_isfile + dtd = 'pythonpoint.dtd' + if not rl_isfile(dtd): + dtd = os.path.join(_RL_DIR,'tools','pythonpoint','pythonpoint.dtd') + if not rl_isfile(dtd): return + def eocb(URI,dtdText=open_and_read(dtd),dtd=dtd): + if os.path.basename(URI)=='pythonpoint.dtd': return dtd,dtdText + return URI + _pyRXP_Parser = pyRXP.Parser(eoCB=eocb) + return _pyRXP_Parser.parse(rawdata) + +def process(datafile, notes=0, handout=0, printout=0, cols=0, verbose=0, outDir=None, datafilename=None, fx=1): + "Process one PythonPoint source file." + if not hasattr(datafile, "read"): + if not datafilename: datafilename = datafile + datafile = open(datafile) + else: + if not datafilename: datafilename = "PseudoFile" + rawdata = datafile.read() + + #if pyRXP present, use it to check and get line numbers for errors... + validate(rawdata) + return _process(rawdata, datafilename, notes, handout, printout, cols, verbose, outDir, fx) + +def _process(rawdata, datafilename, notes=0, handout=0, printout=0, cols=0, verbose=0, outDir=None, fx=1): + #print 'inner process fx=%d' % fx + from reportlab.tools.pythonpoint.stdparser import PPMLParser + parser = PPMLParser() + parser.fx = fx + parser.sourceFilename = datafilename + parser.feed(rawdata) + pres = parser.getPresentation() + pres.sourceFilename = datafilename + pres.outDir = outDir + pres.notes = notes + pres.handout = handout + pres.printout = printout + pres.cols = cols + pres.verbose = verbose + + if printout: + pres.slides = handleHiddenSlides(pres.slides) + + #this does all the work + pdfcontent = pres.save() + + if verbose: + print 'saved presentation %s.pdf' % os.path.splitext(datafilename)[0] + parser.close() + + return pdfcontent +##class P: +## def feed(self, text): +## parser = stdparser.PPMLParser() +## d = pyRXP.parse(text) +## +## +##def process2(datafilename, notes=0, handout=0, cols=0): +## "Process one PythonPoint source file." +## +## import pyRXP, pprint +## +## rawdata = open(datafilename).read() +## d = pyRXP.parse(rawdata) +## pprint.pprint(d) + + +def handleOptions(): + # set defaults + from reportlab import rl_config + options = {'cols':2, + 'handout':0, + 'printout':0, + 'help':0, + 'notes':0, + 'fx':1, + 'verbose':rl_config.verbose, + 'silent':0, + 'outDir': None} + + args = sys.argv[1:] + args = filter(lambda x: x and x[0]=='-',args) + filter(lambda x: not x or x[0]!='-',args) + try: + shortOpts = 'hnvsx' + longOpts = string.split('cols= outdir= handout help notes printout verbose silent nofx') + optList, args = getopt.getopt(args, shortOpts, longOpts) + except getopt.error, msg: + options['help'] = 1 + + if not args and os.path.isfile('pythonpoint.xml'): + args = ['pythonpoint.xml'] + + # Remove leading dashes (max. two). + for i in range(len(optList)): + o, v = optList[i] + while o[0] == '-': + o = o[1:] + optList[i] = (o, v) + + if o == 'cols': options['cols'] = int(v) + elif o=='outdir': options['outDir'] = v + + if filter(lambda ov: ov[0] == 'handout', optList): + options['handout'] = 1 + + if filter(lambda ov: ov[0] == 'printout', optList): + options['printout'] = 1 + + if optList == [] and args == [] or \ + filter(lambda ov: ov[0] in ('h', 'help'), optList): + options['help'] = 1 + + if filter(lambda ov: ov[0] in ('n', 'notes'), optList): + options['notes'] = 1 + + if filter(lambda ov: ov[0] in ('x', 'nofx'), optList): + options['fx'] = 0 + + if filter(lambda ov: ov[0] in ('v', 'verbose'), optList): + options['verbose'] = 1 + + #takes priority over verbose. Used by our test suite etc. + #to ensure no output at all + if filter(lambda ov: ov[0] in ('s', 'silent'), optList): + options['silent'] = 1 + options['verbose'] = 0 + + + return options, args + +def main(): + options, args = handleOptions() + + if options['help']: + print USAGE_MESSAGE + sys.exit(0) + + if options['verbose'] and options['notes']: + print 'speaker notes mode' + + if options['verbose'] and options['handout']: + print 'handout mode' + + if options['verbose'] and options['printout']: + print 'printout mode' + + if not options['fx']: + print 'suppressing special effects' + for fileGlobs in args: + files = glob.glob(fileGlobs) + if not files: + print fileGlobs, "not found" + return + for datafile in files: + if os.path.isfile(datafile): + file = os.path.join(os.getcwd(), datafile) + notes, handout, printout, cols, verbose, fx = options['notes'], options['handout'], options['printout'], options['cols'], options['verbose'], options['fx'] + process(file, notes, handout, printout, cols, verbose, options['outDir'], fx=fx) + else: + print 'Data file not found:', datafile + +if __name__ == '__main__': + main() diff --git a/bin/reportlab/tools/pythonpoint/stdparser.py b/bin/reportlab/tools/pythonpoint/stdparser.py new file mode 100644 index 00000000000..aaca2df2000 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/stdparser.py @@ -0,0 +1,813 @@ +""" +Parser for PythonPoint using the xmllib.py in the standard Python +distribution. Slow, but always present. We intend to add new parsers +as Python 2.x and the XML package spread in popularity and stabilise. + +The parser has a getPresentation method; it is called from +pythonpoint.py. +""" + +import string, imp, sys, os, copy +from reportlab.lib.utils import SeqTypes +from reportlab.lib import xmllib +from reportlab.lib import colors +from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY +from reportlab.lib.utils import recursiveImport +from reportlab.tools.pythonpoint import pythonpoint +from reportlab.platypus import figures + + +def getModule(modulename,fromPath='reportlab.tools.pythonpoint.styles'): + """Get a module containing style declarations. + + Search order is: + reportlab/tools/pythonpoint/ + reportlab/tools/pythonpoint/styles/ + ./ + """ + + try: + exec 'from reportlab.tools.pythonpoint import '+modulename + return eval(modulename) + except ImportError: + try: + exec 'from reportlab.tools.pythonpoint.styles import '+modulename + return eval(modulename) + except ImportError: + exec 'import '+modulename + return eval(modulename) + + +class PPMLParser(xmllib.XMLParser): + attributes = { + #this defines the available attributes for all objects, + #and their default values. Although these don't have to + #be strings, the ones parsed from the XML do, so + #everything is a quoted string and the parser has to + #convert these to numbers where appropriate. + 'stylesheet': { + 'path':'None', + 'module':'None', + 'function':'getParagraphStyles' + }, + 'frame': { + 'x':'0', + 'y':'0', + 'width':'0', + 'height':'0', + 'border':'false', + 'leftmargin':'0', #this is ignored + 'topmargin':'0', #this is ignored + 'rightmargin':'0', #this is ignored + 'bottommargin':'0', #this is ignored + }, + 'slide': { + 'id':'None', + 'title':'None', + 'effectname':'None', # Split, Blinds, Box, Wipe, Dissolve, Glitter + 'effectdirection':'0', # 0,90,180,270 + 'effectdimension':'H', # H or V - horizontal or vertical + 'effectmotion':'I', # Inwards or Outwards + 'effectduration':'1', #seconds, + 'outlineentry':'None', + 'outlinelevel':'0' # 1 is a child, 2 is a grandchild etc. + }, + 'para': { + 'style':'Normal', + 'bullettext':'', + 'effectname':'None', + 'effectdirection':'0', + 'effectdimension':'H', + 'effectmotion':'I', + 'effectduration':'1' + }, + 'image': { + 'filename':'', + 'width':'None', + 'height':'None', + 'effectname':'None', + 'effectdirection':'0', + 'effectdimension':'H', + 'effectmotion':'I', + 'effectduration':'1' + }, + 'table': { + 'widths':'None', + 'heights':'None', + 'fieldDelim':',', + 'rowDelim':'\n', + 'style':'None', + 'effectname':'None', + 'effectdirection':'0', + 'effectdimension':'H', + 'effectmotion':'I', + 'effectduration':'1' + }, + 'rectangle': { + 'x':'0', + 'y':'0', + 'width':'100', + 'height':'100', + 'fill':'None', + 'stroke':'(0,0,0)', + 'linewidth':'0', + 'effectname':'None', + 'effectdirection':'0', + 'effectdimension':'H', + 'effectmotion':'I', + 'effectduration':'1' + }, + 'roundrect': { + 'x':'0', + 'y':'0', + 'width':'100', + 'height':'100', + 'radius':'6', + 'fill':'None', + 'stroke':'(0,0,0)', + 'linewidth':'0', + 'effectname':'None', + 'effectdirection':'0', + 'effectdimension':'H', + 'effectmotion':'I', + 'effectduration':'1' + }, + 'line': { + 'x1':'0', + 'y1':'0', + 'x2':'100', + 'y2':'100', + 'stroke':'(0,0,0)', + 'width':'0', + 'effectname':'None', + 'effectdirection':'0', + 'effectdimension':'H', + 'effectmotion':'I', + 'effectduration':'1' + }, + 'ellipse': { + 'x1':'0', + 'y1':'0', + 'x2':'100', + 'y2':'100', + 'stroke':'(0,0,0)', + 'fill':'None', + 'linewidth':'0', + 'effectname':'None', + 'effectdirection':'0', + 'effectdimension':'H', + 'effectmotion':'I', + 'effectduration':'1' + }, + 'polygon': { + 'points':'(0,0),(50,0),(25,25)', + 'stroke':'(0,0,0)', + 'linewidth':'0', + 'stroke':'(0,0,0)', + 'fill':'None', + 'effectname':'None', + 'effectdirection':'0', + 'effectdimension':'H', + 'effectmotion':'I', + 'effectduration':'1' + }, + 'string':{ + 'x':'0', + 'y':'0', + 'color':'(0,0,0)', + 'font':'Times-Roman', + 'size':'12', + 'align':'left', + 'effectname':'None', + 'effectdirection':'0', + 'effectdimension':'H', + 'effectmotion':'I', + 'effectduration':'1' + }, + 'customshape':{ + 'path':'None', + 'module':'None', + 'class':'None', + 'initargs':'None' + } + } + + def __init__(self): + self.presentations = [] + #yes, I know a generic stack would be easier... + #still, testing if we are 'in' something gives + #a degree of validation. + self._curPres = None + self._curSection = None + self._curSlide = None + self._curFrame = None + self._curPara = None #the only places we are interested in + self._curPrefmt = None + self._curPyCode = None + self._curString = None + self._curTable = None + self._curTitle = None + self._curAuthor = None + self._curSubject = None + self.fx = 1 + xmllib.XMLParser.__init__(self) + + def _arg(self,tag,args,name): + "What's this for???" + if args.has_key(name): + v = args[name] + else: + if self.attributes.has_key(tag): + v = self.attributes[tag][name] + else: + v = None + return v + + def ceval(self,tag,args,name): + if args.has_key(name): + v = args[name] + else: + if self.attributes.has_key(tag): + v = self.attributes[tag][name] + else: + return None + + # handle named colors (names from reportlab.lib.colors) + if name in ('color', 'stroke', 'fill'): + v = str(pythonpoint.checkColor(v)) + + return eval(v) + + def getPresentation(self): + return self._curPres + + + def handle_data(self, data): + #the only data should be paragraph text, preformatted para + #text, 'string text' for a fixed string on the page, + #or table data + + if self._curPara: + self._curPara.rawtext = self._curPara.rawtext + data + elif self._curPrefmt: + self._curPrefmt.rawtext = self._curPrefmt.rawtext + data + elif self._curPyCode: + self._curPyCode.rawtext = self._curPyCode.rawtext + data + elif self._curString: + self._curString.text = self._curString.text + data + elif self._curTable: + self._curTable.rawBlocks.append(data) + elif self._curTitle <> None: # need to allow empty strings, + # hence explicitly testing for None + self._curTitle = self._curTitle + data + elif self._curAuthor <> None: + self._curAuthor = self._curAuthor + data + elif self._curSubject <> None: + self._curSubject = self._curSubject + data + + def handle_cdata(self, data): + #just append to current paragraph text, so we can quote XML + if self._curPara: + self._curPara.rawtext = self._curPara.rawtext + data + elif self._curPrefmt: + self._curPrefmt.rawtext = self._curPrefmt.rawtext + data + elif self._curPyCode: + self._curPyCode.rawtext = self._curPyCode.rawtext + data + elif self._curString: + self._curString.text = self._curString.text + data + elif self._curTable: + self._curTable.rawBlocks.append(data) + elif self._curAuthor <> None: + self._curAuthor = self._curAuthor + data + elif self._curSubject <> None: + self._curSubject = self._curSubject + data + + def start_presentation(self, args): + self._curPres = pythonpoint.PPPresentation() + self._curPres.filename = self._arg('presentation',args,'filename') + self._curPres.effectName = self._arg('presentation',args,'effect') + self._curPres.pageDuration = self._arg('presentation',args,'pageDuration') + + h = self._arg('presentation',args,'pageHeight') + if h: + self._curPres.pageHeight = h + w = self._arg('presentation',args,'pageWidth') + if w: + self._curPres.pageWidth = w + #print 'page size =', self._curPres.pageSize + + def end_presentation(self): + pass +## print 'Fully parsed presentation',self._curPres.filename + + def start_title(self, args): + self._curTitle = '' + + + def end_title(self): + self._curPres.title = self._curTitle + self._curTitle = None + + def start_author(self, args): + self._curAuthor = '' + + def end_author(self): + self._curPres.author = self._curAuthor + self._curAuthor = None + + def start_subject(self, args): + self._curSubject = '' + + def end_subject(self): + self._curPres.subject = self._curSubject + self._curSubject = None + + def start_stylesheet(self, args): + #makes it the current style sheet. + path = self._arg('stylesheet',args,'path') + if path=='None': path = [] + if type(path) not in SeqTypes: path = [path] + path.append('styles') + path.append(os.getcwd()) + modulename = self._arg('stylesheet', args, 'module') + funcname = self._arg('stylesheet', args, 'function') + try: + found = imp.find_module(modulename, path) + (file, pathname, description) = found + mod = imp.load_module(modulename, file, pathname, description) + except ImportError: + #last gasp + mod = getModule(modulename) + + #now get the function + func = getattr(mod, funcname) + pythonpoint.setStyles(func()) +## print 'set global stylesheet to %s.%s()' % (modulename, funcname) + + def end_stylesheet(self): + pass + + def start_section(self, args): + name = self._arg('section',args,'name') + self._curSection = pythonpoint.PPSection(name) + + def end_section(self): + self._curSection = None + + + def start_slide(self, args): + s = pythonpoint.PPSlide() + s.id = self._arg('slide',args,'id') + s.title = self._arg('slide',args,'title') + a = self._arg('slide',args,'effectname') + if a <> 'None': + s.effectName = a + s.effectDirection = self.ceval('slide',args,'effectdirection') + s.effectDimension = self._arg('slide',args,'effectdimension') + s.effectDuration = self.ceval('slide',args,'effectduration') + s.effectMotion = self._arg('slide',args,'effectmotion') + + #HACK - may not belong here in the long run... + #by default, use the slide title for the outline entry, + #unless it is specified as an arg. + a = self._arg('slide',args,'outlineentry') + if a == "Hide": + s.outlineEntry = None + elif a <> 'None': + s.outlineEntry = a + else: + s.outlineEntry = s.title + + s.outlineLevel = self.ceval('slide',args,'outlinelevel') + + #let it know its section, which may be none + s.section = self._curSection + self._curSlide = s + + def end_slide(self): + self._curPres.slides.append(self._curSlide) + self._curSlide = None + + def start_frame(self, args): + self._curFrame = pythonpoint.PPFrame( + self.ceval('frame',args,'x'), + self.ceval('frame',args,'y'), + self.ceval('frame',args,'width'), + self.ceval('frame',args,'height') + ) + if self._arg('frame',args,'border')=='true': + self._curFrame.showBoundary = 1 + + def end_frame(self): + self._curSlide.frames.append(self._curFrame) + self._curFrame = None + + def start_notes(self, args): + name = self._arg('notes',args,'name') + self._curNotes = pythonpoint.PPNotes() + + def end_notes(self): + self._curSlide.notes.append(self._curNotes) + self._curNotes = None + + def start_registerFont(self, args): + name = self._arg('font',args,'name') + path = self._arg('font',args,'path') + pythonpoint.registerFont0(self.sourceFilename, name, path) + + + def end_registerFont(self): + pass + + + def pack_slide(self, element, args): + if self.fx: + effectName = self._arg(element,args,'effectname') + if effectName <> 'None': + curSlide = copy.deepcopy(self._curSlide) + if self._curFrame: + curFrame = copy.deepcopy(self._curFrame) + curSlide.frames.append(curFrame) + self._curPres.slides.append(curSlide) + self._curSlide.effectName = effectName + self._curSlide.effectDirection = self.ceval(element,args,'effectdirection') + self._curSlide.effectDimension = self._arg(element,args,'effectdimension') + self._curSlide.effectDuration = self.ceval(element,args,'effectduration') + self._curSlide.effectMotion = self._arg(element,args,'effectmotion') + self._curSlide.outlineEntry = None + + def start_para(self, args): + self.pack_slide('para', args) + self._curPara = pythonpoint.PPPara() + self._curPara.style = self._arg('para',args,'style') + + # hack - bullet character if bullet style + bt = self._arg('para',args,'bullettext') + if bt == '': + if self._curPara.style == 'Bullet': + bt = '\xc2\xb7' # Symbol Font bullet character, reasonable default + elif self._curPara.style == 'Bullet2': + bt = '\xc2\xb7' # second-level bullet + else: + bt = None + + self._curPara.bulletText = bt + + def end_para(self): + if self._curFrame: + self._curFrame.content.append(self._curPara) + self._curPara = None + elif self._curNotes: + self._curNotes.content.append(self._curPara) + self._curPara = None + + + def start_prefmt(self, args): + self._curPrefmt = pythonpoint.PPPreformattedText() + self._curPrefmt.style = self._arg('prefmt',args,'style') + + + def end_prefmt(self): + self._curFrame.content.append(self._curPrefmt) + self._curPrefmt = None + + + def start_pycode(self, args): + self._curPyCode = pythonpoint.PPPythonCode() + self._curPyCode.style = self._arg('pycode',args,'style') + + + def end_pycode(self): + self._curFrame.content.append(self._curPyCode) + self._curPyCode = None + + + def start_image(self, args): + self.pack_slide('image',args) + sourceFilename = self.sourceFilename # XXX + filename = self._arg('image',args,'filename') + filename = os.path.join(os.path.dirname(sourceFilename), filename) + self._curImage = pythonpoint.PPImage() + self._curImage.filename = filename + self._curImage.width = self.ceval('image',args,'width') + self._curImage.height = self.ceval('image',args,'height') + + + def end_image(self): + self._curFrame.content.append(self._curImage) + self._curImage = None + + + def start_table(self, args): + self.pack_slide('table',args) + self._curTable = pythonpoint.PPTable() + self._curTable.widths = self.ceval('table',args,'widths') + self._curTable.heights = self.ceval('table',args,'heights') + #these may contain escapes like tabs - handle with + #a bit more care. + if args.has_key('fieldDelim'): + self._curTable.fieldDelim = eval('"' + args['fieldDelim'] + '"') + if args.has_key('rowDelim'): + self._curTable.rowDelim = eval('"' + args['rowDelim'] + '"') + if args.has_key('style'): + self._curTable.style = args['style'] + + + def end_table(self): + self._curFrame.content.append(self._curTable) + self._curTable = None + + + def start_spacer(self, args): + """No contents so deal with it here.""" + sp = pythonpoint.PPSpacer() + sp.height = eval(args['height']) + self._curFrame.content.append(sp) + + + def end_spacer(self): + pass + + + ## the graphics objects - go into either the current section + ## or the current slide. + def start_fixedimage(self, args): + sourceFilename = self.sourceFilename + filename = self._arg('image',args,'filename') + filename = os.path.join(os.path.dirname(sourceFilename), filename) + img = pythonpoint.PPFixedImage() + img.filename = filename + img.x = self.ceval('fixedimage',args,'x') + img.y = self.ceval('fixedimage',args,'y') + img.width = self.ceval('fixedimage',args,'width') + img.height = self.ceval('fixedimage',args,'height') + self._curFixedImage = img + + + def end_fixedimage(self): + if self._curSlide: + self._curSlide.graphics.append(self._curFixedImage) + elif self._curSection: + self._curSection.graphics.append(self._curFixedImage) + self._curFixedImage = None + + + def start_rectangle(self, args): + self.pack_slide('rectangle', args) + rect = pythonpoint.PPRectangle( + self.ceval('rectangle',args,'x'), + self.ceval('rectangle',args,'y'), + self.ceval('rectangle',args,'width'), + self.ceval('rectangle',args,'height') + ) + rect.fillColor = self.ceval('rectangle',args,'fill') + rect.strokeColor = self.ceval('rectangle',args,'stroke') + self._curRectangle = rect + + + def end_rectangle(self): + if self._curSlide: + self._curSlide.graphics.append(self._curRectangle) + elif self._curSection: + self._curSection.graphics.append(self._curRectangle) + self._curRectangle = None + + + def start_roundrect(self, args): + self.pack_slide('roundrect', args) + rrect = pythonpoint.PPRoundRect( + self.ceval('roundrect',args,'x'), + self.ceval('roundrect',args,'y'), + self.ceval('roundrect',args,'width'), + self.ceval('roundrect',args,'height'), + self.ceval('roundrect',args,'radius') + ) + rrect.fillColor = self.ceval('roundrect',args,'fill') + rrect.strokeColor = self.ceval('roundrect',args,'stroke') + self._curRoundRect = rrect + + + def end_roundrect(self): + if self._curSlide: + self._curSlide.graphics.append(self._curRoundRect) + elif self._curSection: + self._curSection.graphics.append(self._curRoundRect) + self._curRoundRect = None + + + def start_line(self, args): + self.pack_slide('line', args) + self._curLine = pythonpoint.PPLine( + self.ceval('line',args,'x1'), + self.ceval('line',args,'y1'), + self.ceval('line',args,'x2'), + self.ceval('line',args,'y2') + ) + self._curLine.strokeColor = self.ceval('line',args,'stroke') + + + def end_line(self): + if self._curSlide: + self._curSlide.graphics.append(self._curLine) + elif self._curSection: + self._curSection.graphics.append(self._curLine) + self._curLine = None + + + def start_ellipse(self, args): + self.pack_slide('ellipse', args) + self._curEllipse = pythonpoint.PPEllipse( + self.ceval('ellipse',args,'x1'), + self.ceval('ellipse',args,'y1'), + self.ceval('ellipse',args,'x2'), + self.ceval('ellipse',args,'y2') + ) + self._curEllipse.strokeColor = self.ceval('ellipse',args,'stroke') + self._curEllipse.fillColor = self.ceval('ellipse',args,'fill') + + + def end_ellipse(self): + if self._curSlide: + self._curSlide.graphics.append(self._curEllipse) + elif self._curSection: + self._curSection.graphics.append(self._curEllipse) + self._curEllipse = None + + + def start_polygon(self, args): + self.pack_slide('polygon', args) + self._curPolygon = pythonpoint.PPPolygon(self.ceval('polygon',args,'points')) + self._curPolygon.strokeColor = self.ceval('polygon',args,'stroke') + self._curPolygon.fillColor = self.ceval('polygon',args,'fill') + + + def end_polygon(self): + if self._curSlide: + self._curSlide.graphics.append(self._curPolygon) + elif self._curSection: + self._curSection.graphics.append(self._curPolygon) + self._curEllipse = None + + + def start_string(self, args): + self.pack_slide('string', args) + self._curString = pythonpoint.PPString( + self.ceval('string',args,'x'), + self.ceval('string',args,'y') + ) + self._curString.color = self.ceval('string',args,'color') + self._curString.font = self._arg('string',args,'font') + self._curString.size = self.ceval('string',args,'size') + if args['align'] == 'left': + self._curString.align = TA_LEFT + elif args['align'] == 'center': + self._curString.align = TA_CENTER + elif args['align'] == 'right': + self._curString.align = TA_RIGHT + elif args['align'] == 'justify': + self._curString.align = TA_JUSTIFY + #text comes later within the tag + + + def end_string(self): + #controller should have set the text + if self._curSlide: + self._curSlide.graphics.append(self._curString) + elif self._curSection: + self._curSection.graphics.append(self._curString) + self._curString = None + + + def start_infostring(self, args): + # like a string, but lets them embed page no, author etc. + self.start_string(args) + self._curString.hasInfo = 1 + + + def end_infostring(self): + self.end_string() + + + def start_customshape(self, args): + #loads one + path = self._arg('customshape',args,'path') + if path=='None': + path = [] + else: + path=[path] + + # add package root folder and input file's folder to path + path.append(os.path.dirname(self.sourceFilename)) + path.append(os.path.dirname(pythonpoint.__file__)) + + modulename = self._arg('customshape',args,'module') + funcname = self._arg('customshape',args,'class') + try: + found = imp.find_module(modulename, path) + (file, pathname, description) = found + mod = imp.load_module(modulename, file, pathname, description) + except ImportError: + mod = getModule(modulename) + + #now get the function + + func = getattr(mod, funcname) + initargs = self.ceval('customshape',args,'initargs') + self._curCustomShape = apply(func, initargs) + + def end_customshape(self): + if self._curSlide: + self._curSlide.graphics.append(self._curCustomShape) + elif self._curSection: + self._curSection.graphics.append(self._curCustomShape) + self._curCustomShape = None + + def start_drawing(self, args): + #loads one + moduleName = args["module"] + funcName = args["constructor"] + showBoundary = int(args.get("showBoundary", "0")) + hAlign = args.get("hAlign", "CENTER") + + + # the path for the imports should include: + # 1. document directory + # 2. python path if baseDir not given, or + # 3. baseDir if given + try: + dirName = sdict["baseDir"] + except: + dirName = None + importPath = [os.getcwd()] + if dirName is None: + importPath.extend(sys.path) + else: + importPath.insert(0, dirName) + + modul = recursiveImport(moduleName, baseDir=importPath) + func = getattr(modul, funcName) + drawing = func() + + drawing.hAlign = hAlign + if showBoundary: + drawing._showBoundary = 1 + + self._curDrawing = pythonpoint.PPDrawing() + self._curDrawing.drawing = drawing + + def end_drawing(self): + self._curFrame.content.append(self._curDrawing) + self._curDrawing = None + + def start_pageCatcherFigure(self, args): + filename = args["filename"] + pageNo = int(args["pageNo"]) + width = float(args.get("width", "595")) + height = float(args.get("height", "842")) + + + fig = figures.PageCatcherFigureNonA4(filename, pageNo, args.get("caption", ""), width, height) + sf = args.get('scaleFactor', None) + if sf: sf = float(sf) + border = not (args.get('border', None) in ['0','no']) + + fig.scaleFactor = sf + fig.border = border + + #self.ceval('pageCatcherFigure',args,'scaleFactor'), + #initargs = self.ceval('customshape',args,'initargs') + self._curFigure = pythonpoint.PPFigure() + self._curFigure.figure = fig + + def end_pageCatcherFigure(self): + self._curFrame.content.append(self._curFigure) + self._curFigure = None + + ## intra-paragraph XML should be allowed through into PLATYPUS + def unknown_starttag(self, tag, attrs): + if self._curPara: + echo = '<%s' % tag + for (key, value) in attrs.items(): + echo = echo + ' %s="%s"' % (key, value) + echo = echo + '>' + self._curPara.rawtext = self._curPara.rawtext + echo + else: + print 'Unknown start tag %s' % tag + + + def unknown_endtag(self, tag): + if self._curPara: + self._curPara.rawtext = self._curPara.rawtext + ''% tag + else: + print 'Unknown end tag %s' % tag + + def handle_charref(self, name): + try: + if name[0]=='x': + n = int(name[1:],16) + else: + n = int(name) + except ValueError: + self.unknown_charref(name) + return + self.handle_data(unichr(n).encode('utf8')) diff --git a/bin/reportlab/tools/pythonpoint/styles/__init__.py b/bin/reportlab/tools/pythonpoint/styles/__init__.py new file mode 100644 index 00000000000..e67468fd9ee --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/styles/__init__.py @@ -0,0 +1,3 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/pythonpoint/styles/__init__.py diff --git a/bin/reportlab/tools/pythonpoint/styles/horrible.py b/bin/reportlab/tools/pythonpoint/styles/horrible.py new file mode 100644 index 00000000000..fa6d501b043 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/styles/horrible.py @@ -0,0 +1,101 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/pythonpoint/styles/horrible.py +__version__=''' $Id: horrible.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' +# style_modern.py +__doc__="""This is an example style sheet. You can create your own, and +have them loaded by the presentation. A style sheet is just a +dictionary, where they keys are style names and the values are +ParagraphStyle objects. + +You must provide a function called "getParagraphStyles()" to +return it. In future, we can put things like LineStyles, +TableCellStyles etc. in the same modules. + +You might wish to have two parallel style sheets, one for colour +and one for black and white, so you can switch your presentations +easily. + +A style sheet MUST define a style called 'Normal'. +""" + +from reportlab.lib import styles, enums +def getParagraphStyles(): + """Returns a dictionary of styles based on Helvetica""" + stylesheet = {} + + para = styles.ParagraphStyle('Normal', None) #the ancestor of all + para.fontName = 'Courier' + para.fontSize = 24 + para.leading = 28 + stylesheet['Normal'] = para + + para = ParagraphStyle('BodyText', stylesheet['Normal']) + para.spaceBefore = 12 + stylesheet['BodyText'] = para + + para = ParagraphStyle('BigCentered', stylesheet['Normal']) + para.spaceBefore = 12 + para.alignment = enums.TA_CENTER + stylesheet['BigCentered'] = para + + para = ParagraphStyle('Italic', stylesheet['BodyText']) + para.fontName = 'Courier-Oblique' + stylesheet['Italic'] = para + + para = ParagraphStyle('Title', stylesheet['Normal']) + para.fontName = 'Courier' + para.fontSize = 48 + para.Leading = 58 + para.spaceAfter = 36 + para.alignment = enums.TA_CENTER + stylesheet['Title'] = para + + para = ParagraphStyle('Heading1', stylesheet['Normal']) + para.fontName = 'Courier-Bold' + para.fontSize = 36 + para.leading = 44 + para.spaceAfter = 36 + para.alignment = enums.TA_CENTER + stylesheet['Heading1'] = para + + para = ParagraphStyle('Heading2', stylesheet['Normal']) + para.fontName = 'Courier-Bold' + para.fontSize = 28 + para.leading = 34 + para.spaceBefore = 24 + para.spaceAfter = 12 + stylesheet['Heading2'] = para + + para = ParagraphStyle('Heading3', stylesheet['Normal']) + para.fontName = 'Courier-BoldOblique' + para.spaceBefore = 24 + para.spaceAfter = 12 + stylesheet['Heading3'] = para + + para = ParagraphStyle('Bullet', stylesheet['Normal']) + para.firstLineIndent = -18 + para.leftIndent = 72 + para.spaceBefore = 6 + #para.bulletFontName = 'Symbol' + para.bulletFontSize = 24 + para.bulletIndent = 36 + stylesheet['Bullet'] = para + + para = ParagraphStyle('Definition', stylesheet['Normal']) + #use this for definition lists + para.firstLineIndent = 0 + para.leftIndent = 72 + para.bulletIndent = 0 + para.spaceBefore = 12 + para.bulletFontName = 'Couruer-BoldOblique' + stylesheet['Definition'] = para + + para = ParagraphStyle('Code', stylesheet['Normal']) + para.fontName = 'Courier' + para.fontSize = 16 + para.leading = 18 + para.leftIndent = 36 + stylesheet['Code'] = para + + return stylesheet \ No newline at end of file diff --git a/bin/reportlab/tools/pythonpoint/styles/htu.py b/bin/reportlab/tools/pythonpoint/styles/htu.py new file mode 100644 index 00000000000..bc79a7ba68e --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/styles/htu.py @@ -0,0 +1,158 @@ +from reportlab.lib import styles +from reportlab.lib import colors +from reportlab.lib.units import cm +from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY +from reportlab.platypus import Preformatted, Paragraph, Frame, \ + Image, Table, TableStyle, Spacer +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont + + +def getParagraphStyles(): + """Returns a dictionary of styles to get you started. + + We will provide a way to specify a module of these. Note that + this just includes TableStyles as well as ParagraphStyles for any + tables you wish to use. + """ + + pdfmetrics.registerFont(TTFont('Verdana','verdana.ttf')) + pdfmetrics.registerFont(TTFont('Verdana-Bold','verdanab.ttf')) + pdfmetrics.registerFont(TTFont('Verdana-Italic','verdanai.ttf')) + pdfmetrics.registerFont(TTFont('Verdana-BoldItalic','verdanaz.ttf')) + pdfmetrics.registerFont(TTFont('Arial Narrow','arialn.ttf')) + pdfmetrics.registerFont(TTFont('Arial Narrow-Bold','arialnb.ttf')) + pdfmetrics.registerFont(TTFont('Arial Narrow-Italic','arialni.ttf')) + pdfmetrics.registerFont(TTFont('Arial Narrow-BoldItalic','arialnbi.ttf')) + + stylesheet = {} + ParagraphStyle = styles.ParagraphStyle + + para = ParagraphStyle('Normal', None) #the ancestor of all + para.fontName = 'Verdana' + para.fontSize = 28 + para.leading = 32 + para.spaceAfter = 6 + stylesheet['Normal'] = para + + #This one is spaced out a bit... + para = ParagraphStyle('BodyText', stylesheet['Normal']) + para.spaceBefore = 12 + stylesheet['BodyText'] = para + + #Indented, for lists + para = ParagraphStyle('Indent', stylesheet['Normal']) + para.leftIndent = 60 + para.firstLineIndent = 0 + stylesheet['Indent'] = para + + para = ParagraphStyle('Centered', stylesheet['Normal']) + para.alignment = TA_CENTER + stylesheet['Centered'] = para + + para = ParagraphStyle('BigCentered', stylesheet['Normal']) + para.fontSize = 32 + para.alignment = TA_CENTER + para.spaceBefore = 12 + para.spaceAfter = 12 + stylesheet['BigCentered'] = para + + para = ParagraphStyle('Italic', stylesheet['BodyText']) + para.fontName = 'Verdana-Italic' + stylesheet['Italic'] = para + + para = ParagraphStyle('Title', stylesheet['Normal']) + para.fontName = 'Arial Narrow-Bold' + para.fontSize = 48 + para.leading = 58 + para.alignment = TA_CENTER + stylesheet['Title'] = para + + para = ParagraphStyle('Heading1', stylesheet['Normal']) + para.fontName = 'Arial Narrow-Bold' + para.fontSize = 40 + para.leading = 44 + para.alignment = TA_CENTER + stylesheet['Heading1'] = para + + para = ParagraphStyle('Heading2', stylesheet['Normal']) + para.fontName = 'Verdana' + para.fontSize = 32 + para.leading = 36 + para.spaceBefore = 32 + para.spaceAfter = 12 + stylesheet['Heading2'] = para + + para = ParagraphStyle('Heading3', stylesheet['Normal']) + para.fontName = 'Verdana' + para.spaceBefore = 20 + para.spaceAfter = 6 + stylesheet['Heading3'] = para + + para = ParagraphStyle('Heading4', stylesheet['Normal']) + para.fontName = 'Verdana-BoldItalic' + para.spaceBefore = 6 + stylesheet['Heading4'] = para + + para = ParagraphStyle('Bullet', stylesheet['Normal']) + para.firstLineIndent = 0 + para.leftIndent = 56 + para.spaceBefore = 6 + para.bulletFontName = 'Symbol' + para.bulletFontSize = 24 + para.bulletIndent = 20 + stylesheet['Bullet'] = para + + para = ParagraphStyle('Bullet2', stylesheet['Normal']) + para.firstLineIndent = 0 + para.leftIndent = 80 + para.spaceBefore = 6 + para.fontSize = 24 + para.bulletFontName = 'Symbol' + para.bulletFontSize = 20 + para.bulletIndent = 60 + stylesheet['Bullet2'] = para + + para = ParagraphStyle('Definition', stylesheet['Normal']) + #use this for definition lists + para.firstLineIndent = 0 + para.leftIndent = 60 + para.bulletIndent = 0 + para.bulletFontName = 'Verdana-BoldItalic' + para.bulletFontSize = 24 + stylesheet['Definition'] = para + + para = ParagraphStyle('Code', stylesheet['Normal']) + para.fontName = 'Courier' + para.fontSize = 16 + para.leading = 18 + para.leftIndent = 36 + stylesheet['Code'] = para + + para = ParagraphStyle('PythonCode', stylesheet['Normal']) + para.fontName = 'Courier' + para.fontSize = 16 + para.leading = 18 + para.leftIndent = 36 + stylesheet['Code'] = para + + para = ParagraphStyle('Small', stylesheet['Normal']) + para.fontSize = 12 + para.leading = 14 + stylesheet['Small'] = para + + #now for a table + ts = TableStyle([ + ('FONT', (0,0), (-1,-1), 'Arial Narrow', 22), + ('LINEABOVE', (0,1), (-1,1), 2, colors.green), + ('LINEABOVE', (0,2), (-1,-1), 0.25, colors.black), + ('LINEBELOW', (0,-1), (-1,-1), 2, colors.green), + ('LINEBEFORE', (0,1), (-1,-1), 2, colors.black), + ('LINEAFTER', (0,1), (-1,-1), 2, colors.black), + ('ALIGN', (4,1), (-1,-1), 'RIGHT'), #all numeric cells right aligned + ('TEXTCOLOR', (0,2), (0,-1), colors.black), + ('BACKGROUND', (0,1), (-1,1), colors.Color(0,0.7,0.7)) + ]) + stylesheet['table1'] = ts + + return stylesheet diff --git a/bin/reportlab/tools/pythonpoint/styles/modern.py b/bin/reportlab/tools/pythonpoint/styles/modern.py new file mode 100644 index 00000000000..97583b57857 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/styles/modern.py @@ -0,0 +1,120 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/pythonpoint/styles/modern.py +__version__=''' $Id: modern.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' +# style_modern.py +__doc__="""This is an example style sheet. You can create your own, and +have them loaded by the presentation. A style sheet is just a +dictionary, where they keys are style names and the values are +ParagraphStyle objects. + +You must provide a function called "getParagraphStyles()" to +return it. In future, we can put things like LineStyles, +TableCellStyles etc. in the same modules. + +You might wish to have two parallel style sheets, one for colour +and one for black and white, so you can switch your presentations +easily. + +A style sheet MUST define a style called 'Normal'. +""" + +from reportlab.lib import styles +from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY + +def getParagraphStyles(): + """Returns a dictionary of styles based on Helvetica""" + stylesheet = {} + ParagraphStyle = styles.ParagraphStyle + + para = ParagraphStyle('Normal', None) #the ancestor of all + para.fontName = 'Helvetica' + para.fontSize = 24 + para.leading = 28 + stylesheet['Normal'] = para + + para = ParagraphStyle('BodyText', stylesheet['Normal']) + para.spaceBefore = 12 + stylesheet['BodyText'] = para + + para = ParagraphStyle('Indent', stylesheet['Normal']) + para.leftIndent = 36 + para.firstLineIndent = 0 + stylesheet['Indent'] = para + + para = ParagraphStyle('Centered', stylesheet['Normal']) + para.alignment = TA_CENTER + stylesheet['Centered'] = para + + para = ParagraphStyle('BigCentered', stylesheet['Normal']) + para.spaceBefore = 12 + para.alignment = TA_CENTER + stylesheet['BigCentered'] = para + + para = ParagraphStyle('Italic', stylesheet['BodyText']) + para.fontName = 'Helvetica-Oblique' + stylesheet['Italic'] = para + + para = ParagraphStyle('Title', stylesheet['Normal']) + para.fontName = 'Helvetica' + para.fontSize = 48 + para.Leading = 58 + para.spaceAfter = 36 + para.alignment = TA_CENTER + stylesheet['Title'] = para + + para = ParagraphStyle('Heading1', stylesheet['Normal']) + para.fontName = 'Helvetica-Bold' + para.fontSize = 36 + para.leading = 44 + para.spaceAfter = 36 + para.alignment = TA_CENTER + stylesheet['Heading1'] = para + + para = ParagraphStyle('Heading2', stylesheet['Normal']) + para.fontName = 'Helvetica-Bold' + para.fontSize = 28 + para.leading = 34 + para.spaceBefore = 24 + para.spaceAfter = 12 + stylesheet['Heading2'] = para + + para = ParagraphStyle('Heading3', stylesheet['Normal']) + para.fontName = 'Helvetica-BoldOblique' + para.spaceBefore = 24 + para.spaceAfter = 12 + stylesheet['Heading3'] = para + + para = ParagraphStyle('Bullet', stylesheet['Normal']) + para.firstLineIndent = -18 + para.leftIndent = 72 + para.spaceBefore = 6 + para.bulletFontName = 'Symbol' + para.bulletFontSize = 24 + para.bulletIndent = 36 + stylesheet['Bullet'] = para + + para = ParagraphStyle('Bullet2', stylesheet['Bullet']) + para.firstLineIndent = 0 + para.bulletIndent = 72 + para.leftIndent = 108 + stylesheet['Bullet2'] = para + + + para = ParagraphStyle('Definition', stylesheet['Normal']) + #use this for definition lists + para.firstLineIndent = 0 + para.leftIndent = 72 + para.bulletIndent = 0 + para.spaceBefore = 12 + para.bulletFontName = 'Helvetica-BoldOblique' + stylesheet['Definition'] = para + + para = ParagraphStyle('Code', stylesheet['Normal']) + para.fontName = 'Courier' + para.fontSize = 16 + para.leading = 18 + para.leftIndent = 36 + stylesheet['Code'] = para + + return stylesheet \ No newline at end of file diff --git a/bin/reportlab/tools/pythonpoint/styles/projection.py b/bin/reportlab/tools/pythonpoint/styles/projection.py new file mode 100644 index 00000000000..a3c818556d0 --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/styles/projection.py @@ -0,0 +1,106 @@ +"""This is an example style sheet. You can create your own, and +have them loaded by the presentation. A style sheet is just a +dictionary, where they keys are style names and the values are +ParagraphStyle objects. + +You must provide a function called "getParagraphStyles()" to +return it. In future, we can put things like LineStyles, +TableCellStyles etc. in the same modules. + +You might wish to have two parallel style sheets, one for colour +and one for black and white, so you can switch your presentations +easily. + +A style sheet MUST define a style called 'Normal'. +""" + +from reportlab.lib import styles +from reportlab.lib.colors import * +from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY + + +def getParagraphStyles(): + """Returns a dictionary of styles based on Helvetica""" + + stylesheet = {} + ParagraphStyle = styles.ParagraphStyle + + para = ParagraphStyle('Normal', None) #the ancestor of all + para.fontName = 'Helvetica-Bold' + para.fontSize = 24 + para.leading = 28 + para.textColor = white + stylesheet['Normal'] = para + + para = ParagraphStyle('BodyText', stylesheet['Normal']) + para.spaceBefore = 12 + stylesheet['BodyText'] = para + + para = ParagraphStyle('BigCentered', stylesheet['Normal']) + para.spaceBefore = 12 + para.alignment = TA_CENTER + stylesheet['BigCentered'] = para + + para = ParagraphStyle('Italic', stylesheet['BodyText']) + para.fontName = 'Helvetica-Oblique' + para.textColor = white + stylesheet['Italic'] = para + + para = ParagraphStyle('Title', stylesheet['Normal']) + para.fontName = 'Helvetica' + para.fontSize = 48 + para.Leading = 58 + para.spaceAfter = 36 + para.alignment = TA_CENTER + stylesheet['Title'] = para + + para = ParagraphStyle('Heading1', stylesheet['Normal']) + para.fontName = 'Helvetica-Bold' + para.fontSize = 48# 36 + para.leading = 44 + para.spaceAfter = 36 + para.textColor = green + para.alignment = TA_LEFT + stylesheet['Heading1'] = para + + para = ParagraphStyle('Heading2', stylesheet['Normal']) + para.fontName = 'Helvetica-Bold' + para.fontSize = 28 + para.leading = 34 + para.spaceBefore = 24 + para.spaceAfter = 12 + stylesheet['Heading2'] = para + + para = ParagraphStyle('Heading3', stylesheet['Normal']) + para.fontName = 'Helvetica-BoldOblique' + para.spaceBefore = 24 + para.spaceAfter = 12 + stylesheet['Heading3'] = para + + para = ParagraphStyle('Bullet', stylesheet['Normal']) + para.firstLineIndent = -18 + para.leftIndent = 72 + para.spaceBefore = 6 + para.bulletFontName = 'Symbol' + para.bulletFontSize = 24 + para.bulletIndent = 36 + stylesheet['Bullet'] = para + + para = ParagraphStyle('Definition', stylesheet['Normal']) + #use this for definition lists + para.firstLineIndent = 0 + para.leftIndent = 72 + para.bulletIndent = 0 + para.spaceBefore = 12 + para.bulletFontName = 'Helvetica-BoldOblique' + stylesheet['Definition'] = para + + para = ParagraphStyle('Code', stylesheet['Normal']) + para.fontName = 'Courier-Bold' + para.fontSize = 16 + para.leading = 18 + para.leftIndent = 36 + para.textColor = chartreuse + stylesheet['Code'] = para + + return stylesheet \ No newline at end of file diff --git a/bin/reportlab/tools/pythonpoint/styles/standard.py b/bin/reportlab/tools/pythonpoint/styles/standard.py new file mode 100644 index 00000000000..c525964585d --- /dev/null +++ b/bin/reportlab/tools/pythonpoint/styles/standard.py @@ -0,0 +1,132 @@ +from reportlab.lib import styles +from reportlab.lib import colors +from reportlab.lib.units import cm +from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY +from reportlab.platypus import Preformatted, Paragraph, Frame, \ + Image, Table, TableStyle, Spacer + + +def getParagraphStyles(): + """Returns a dictionary of styles to get you started. + + We will provide a way to specify a module of these. Note that + this just includes TableStyles as well as ParagraphStyles for any + tables you wish to use. + """ + + stylesheet = {} + ParagraphStyle = styles.ParagraphStyle + + para = ParagraphStyle('Normal', None) #the ancestor of all + para.fontName = 'Times-Roman' + para.fontSize = 24 + para.leading = 28 + stylesheet['Normal'] = para + + #This one is spaced out a bit... + para = ParagraphStyle('BodyText', stylesheet['Normal']) + para.spaceBefore = 12 + stylesheet['BodyText'] = para + + #Indented, for lists + para = ParagraphStyle('Indent', stylesheet['Normal']) + para.leftIndent = 36 + para.firstLineIndent = 0 + stylesheet['Indent'] = para + + para = ParagraphStyle('Centered', stylesheet['Normal']) + para.alignment = TA_CENTER + stylesheet['Centered'] = para + + para = ParagraphStyle('BigCentered', stylesheet['Normal']) + para.spaceBefore = 12 + para.alignment = TA_CENTER + stylesheet['BigCentered'] = para + + para = ParagraphStyle('Italic', stylesheet['BodyText']) + para.fontName = 'Times-Italic' + stylesheet['Italic'] = para + + para = ParagraphStyle('Title', stylesheet['Normal']) + para.fontName = 'Times-Roman' + para.fontSize = 48 + para.leading = 58 + para.alignment = TA_CENTER + stylesheet['Title'] = para + + para = ParagraphStyle('Heading1', stylesheet['Normal']) + para.fontName = 'Times-Bold' + para.fontSize = 36 + para.leading = 44 + para.alignment = TA_CENTER + stylesheet['Heading1'] = para + + para = ParagraphStyle('Heading2', stylesheet['Normal']) + para.fontName = 'Times-Bold' + para.fontSize = 28 + para.leading = 34 + para.spaceBefore = 24 + stylesheet['Heading2'] = para + + para = ParagraphStyle('Heading3', stylesheet['Normal']) + para.fontName = 'Times-BoldItalic' + para.spaceBefore = 24 + stylesheet['Heading3'] = para + + para = ParagraphStyle('Heading4', stylesheet['Normal']) + para.fontName = 'Times-BoldItalic' + para.spaceBefore = 6 + stylesheet['Heading4'] = para + + para = ParagraphStyle('Bullet', stylesheet['Normal']) + para.firstLineIndent = 0 + para.leftIndent = 56 + para.spaceBefore = 6 + para.bulletFontName = 'Symbol' + para.bulletFontSize = 24 + para.bulletIndent = 20 + stylesheet['Bullet'] = para + + para = ParagraphStyle('Definition', stylesheet['Normal']) + #use this for definition lists + para.firstLineIndent = 0 + para.leftIndent = 72 + para.bulletIndent = 0 + para.spaceBefore = 12 + para.bulletFontName = 'Helvetica-BoldOblique' + para.bulletFontSize = 24 + stylesheet['Definition'] = para + + para = ParagraphStyle('Code', stylesheet['Normal']) + para.fontName = 'Courier' + para.fontSize = 16 + para.leading = 18 + para.leftIndent = 36 + stylesheet['Code'] = para + + para = ParagraphStyle('PythonCode', stylesheet['Normal']) + para.fontName = 'Courier' + para.fontSize = 16 + para.leading = 18 + para.leftIndent = 36 + stylesheet['PythonCode'] = para + + para = ParagraphStyle('Small', stylesheet['Normal']) + para.fontSize = 12 + para.leading = 14 + stylesheet['Small'] = para + + #now for a table + ts = TableStyle([ + ('FONT', (0,0), (-1,-1), 'Times-Roman', 24), + ('LINEABOVE', (0,0), (-1,0), 2, colors.green), + ('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + ('LINEBELOW', (0,-1), (-1,-1), 2, colors.green), + ('LINEBEFORE', (-1,0), (-1,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), #all numeric cells right aligned + ('TEXTCOLOR', (0,1), (0,-1), colors.red), + ('BACKGROUND', (0,0), (-1,0), colors.Color(0,0.7,0.7)) + ]) + stylesheet['table1'] = ts + + return stylesheet diff --git a/bin/reportlab/tools/utils/add_bleed.py b/bin/reportlab/tools/utils/add_bleed.py new file mode 100644 index 00000000000..ab4a7824628 --- /dev/null +++ b/bin/reportlab/tools/utils/add_bleed.py @@ -0,0 +1,28 @@ +#How to add bleed to a page in this case 6mm to a landscape A4 +from reportlab.lib import units, pagesizes +from reportlab.pdfgen.canvas import Canvas +import sys, os, glob, time +bleedX = 6*units.mm +bleedY = 6*units.mm +pageWidth, pageHeight = pagesizes.landscape(pagesizes.A4) +def process_pdf(c,infn,prefix='PageForms'): + from rlextra.pageCatcher import pageCatcher + names, data = pageCatcher.storeFormsInMemory(open(infn,'rb').read(),prefix=prefix,all=1) + names = pageCatcher.restoreFormsInMemory(data,c) + del data + for i in xrange(len(names)): + thisname = names[i] + c.saveState() + c.translate(bleedX,bleedY) + c.doForm(thisname) + c.restoreState() + c.showPage() + +def main(): + for infn in sys.argv[1:]: + outfn = 'bleeding_'+os.path.basename(infn) + c = Canvas(outfn,pagesize=(pageWidth+2*bleedX,pageHeight+2*bleedY)) + process_pdf(c,infn) + c.save() +if __name__=='__main__': + main() diff --git a/bin/reportlab/tools/utils/dumpttf.py b/bin/reportlab/tools/utils/dumpttf.py new file mode 100644 index 00000000000..8cb4610c5af --- /dev/null +++ b/bin/reportlab/tools/utils/dumpttf.py @@ -0,0 +1,60 @@ +__all__=('dumpttf',) +def dumpttf(fn,fontName=None, verbose=0): + '''dump out known glyphs from a ttf file''' + import os + if not os.path.isfile(fn): + raise IOError('No such file "%s"' % fn) + from reportlab.pdfbase.pdfmetrics import registerFont, stringWidth + from reportlab.pdfbase.ttfonts import TTFont + from reportlab.pdfgen.canvas import Canvas + if fontName is None: + fontName = os.path.splitext(os.path.basename(fn))[0] + dmpfn = '%s-ttf-dump.pdf' % fontName + ttf = TTFont(fontName, fn) + K = ttf.face.charToGlyph.keys() + registerFont(ttf) + c = Canvas(dmpfn) + W,H = c._pagesize + titleFontSize = 30 # title font size + titleFontName = 'Helvetica' + labelFontName = 'Courier' + fontSize = 10 + border = 36 + dx0 = stringWidth('12345: ', fontName, fontSize) + dx = dx0+20 + dy = 20 + K.sort() + y = 0 + page = 0 + for i, k in enumerate(K): + if yW-border: + x = border + y -= dy + c.showPage() + c.save() + if verbose: + print 'Font %s("%s") has %d glyphs\ndumped to "%s"' % (fontName,fn,len(K),dmpfn) + +if __name__=='__main__': + import sys, glob + if '--verbose' in sys.argv: + sys.argv.remove('--verbose') + verbose = 1 + else: + verbose = 0 + for a in sys.argv[1:]: + for fn in glob.glob(a): + dumpttf(fn, verbose=verbose)