diff --git a/bin/reportlab/test/00readme.txt b/bin/reportlab/test/00readme.txt new file mode 100644 index 00000000000..1b1a0b3c6e2 --- /dev/null +++ b/bin/reportlab/test/00readme.txt @@ -0,0 +1,20 @@ +This directory should contain all test scripts. + +All test scripts should be named like test_*.py, where * describes which +parts of the ReportLab toolkit are being tested (see sample test scripts). + +The test scripts are expected to make use of the PyUnit test environment +named unittest (see pyunit.sourceforge.net). For convenience this comes +bundled with the ReportLab toolkit in the reportlab.test subpackage. + +As of now, this folder has a flat structure, but it might be restructured +in the future as the amount of tests will grow dramatically. + +The file runAll.py begins by deleting any files with extension ".pdf" or +".log" in the test directory, so you can't confuse old and current +test output. It then loops over all test scripts following the +aforementioned pattern and executes them. + +Any PDF or log files written should be examined, at least before +major releases. If you are writing tests, ensure that you only +leave behind files which you intend a human to check. diff --git a/bin/reportlab/test/__init__.py b/bin/reportlab/test/__init__.py new file mode 100644 index 00000000000..64ff636921c --- /dev/null +++ b/bin/reportlab/test/__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/test/__init__.py diff --git a/bin/reportlab/test/pythonpowered.gif b/bin/reportlab/test/pythonpowered.gif new file mode 100644 index 00000000000..34e774c937e Binary files /dev/null and b/bin/reportlab/test/pythonpowered.gif differ diff --git a/bin/reportlab/test/runAll.py b/bin/reportlab/test/runAll.py new file mode 100644 index 00000000000..390fb444d6b --- /dev/null +++ b/bin/reportlab/test/runAll.py @@ -0,0 +1,93 @@ +#!/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/test/runAll.py +"""Runs all test files in all subfolders. +""" +import os, glob, sys, string, traceback +from reportlab.test import unittest +from reportlab.test.utils import GlobDirectoryWalker, outputfile, printLocation + +def makeSuite(folder, exclude=[],nonImportable=[],pattern='test_*.py'): + "Build a test suite of all available test files." + + allTests = unittest.TestSuite() + + if os.path.isdir(folder): sys.path.insert(0, folder) + for filename in GlobDirectoryWalker(folder, pattern): + modname = os.path.splitext(os.path.basename(filename))[0] + if modname not in exclude: + try: + exec 'import %s as module' % modname + allTests.addTest(module.makeSuite()) + except: + tt, tv, tb = sys.exc_info()[:] + nonImportable.append((filename,traceback.format_exception(tt,tv,tb))) + del tt,tv,tb + del sys.path[0] + + return allTests + + +def main(pattern='test_*.py'): + try: + folder = os.path.dirname(__file__) + assert folder + except: + folder = os.path.dirname(sys.argv[0]) or os.getcwd() + #allow for Benn's "screwball cygwin distro": + if folder == '': + folder = '.' + from reportlab.lib.utils import isSourceDistro + haveSRC = isSourceDistro() + + def cleanup(folder,patterns=('*.pdf', '*.log','*.svg','runAll.txt', 'test_*.txt','_i_am_actually_a_*.*')): + if not folder: return + for pat in patterns: + for filename in GlobDirectoryWalker(folder, pattern=pat): + try: + os.remove(filename) + except: + pass + + # special case for reportlab/test directory - clean up + # all PDF & log files before starting run. You don't + # want this if reusing runAll anywhere else. + if string.find(folder, 'reportlab' + os.sep + 'test') > -1: cleanup(folder) + cleanup(outputfile('')) + NI = [] + cleanOnly = '--clean' in sys.argv + if not cleanOnly: + testSuite = makeSuite(folder,nonImportable=NI,pattern=pattern+(not haveSRC and 'c' or '')) + unittest.TextTestRunner().run(testSuite) + if haveSRC: cleanup(folder,patterns=('*.pyc','*.pyo')) + if not cleanOnly: + if NI: + sys.stderr.write('\n###################### the following tests could not be imported\n') + for f,tb in NI: + print 'file: "%s"\n%s\n' % (f,string.join(tb,'')) + printLocation() + +def mainEx(): + '''for use in subprocesses''' + try: + main() + finally: + sys.stdout.flush() + sys.stderr.flush() + sys.stdout.close() + os.close(sys.stderr.fileno()) + +def runExternally(): + cmd = sys.executable+' -c"from reportlab.test import runAll;runAll.mainEx()"' + i,o,e=os.popen3(cmd) + i.close() + out = o.read() + err=e.read() + return '\n'.join((out,err)) + +def checkForFailure(outerr): + return '\nFAILED' in outerr + +if __name__ == '__main__': #noruntests + main() diff --git a/bin/reportlab/test/test_charts_textlabels.py b/bin/reportlab/test/test_charts_textlabels.py new file mode 100644 index 00000000000..51407220147 --- /dev/null +++ b/bin/reportlab/test/test_charts_textlabels.py @@ -0,0 +1,279 @@ +#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/test/test_charts_textlabels.py +""" +Tests for the text Label class. +""" + +import os, sys, copy +from os.path import join, basename, splitext + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation +from reportlab.lib import colors +from reportlab.lib.units import cm +from reportlab.lib.pagesizes import A4 +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.pdfgen.canvas import Canvas +from reportlab.graphics.shapes import * +from reportlab.graphics.charts.textlabels import Label +from reportlab.platypus.flowables import Spacer, PageBreak +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.xpreformatted import XPreformatted +from reportlab.platypus.frames import Frame +from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate + + +def myMainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + + #canvas.rect(2.5*cm, 2.5*cm, 15*cm, 25*cm) + canvas.setFont('Times-Roman', 12) + pageNumber = canvas.getPageNumber() + canvas.drawString(10*cm, cm, str(pageNumber)) + + canvas.restoreState() + + +class MyDocTemplate(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) + template = PageTemplate('normal', [frame1], myMainPageFrame) + self.addPageTemplates(template) + + +class LabelTestCase(unittest.TestCase): + "Test Label class." + + def _test0(self): + "Perform original test function." + + pdfPath = outputfile('test_charts_textlabels.pdf') + c = Canvas(pdfPath) + + label = Label() + demoLabel = label.demo() + demoLabel.drawOn(c, 0, 0) + + c.save() + + + def _makeProtoLabel(self): + "Return a label prototype for further modification." + + protoLabel = Label() + protoLabel.dx = 0 + protoLabel.dy = 0 + protoLabel.boxStrokeWidth = 0.1 + protoLabel.boxStrokeColor = colors.black + protoLabel.boxFillColor = colors.yellow + # protoLabel.text = 'Hello World!' # Does not work as expected. + + return protoLabel + + + def _makeDrawings(self, protoLabel, text=None): + # Set drawing dimensions. + w, h = drawWidth, drawHeight = 400, 100 + + drawings = [] + + for boxAnchors in ('sw se nw ne', 'w e n s', 'c'): + boxAnchors = string.split(boxAnchors, ' ') + + # Create drawing. + d = Drawing(w, h) + d.add(Line(0,h/2, w, h/2, strokeColor=colors.gray, strokeWidth=0.5)) + d.add(Line(w/2,0, w/2, h, strokeColor=colors.gray, strokeWidth=0.5)) + + labels = [] + for boxAnchor in boxAnchors: + # Modify label, put it on a drawing. + label = copy.deepcopy(protoLabel) + label.boxAnchor = boxAnchor + args = {'ba':boxAnchor, 'text':text or 'Hello World!'} + label.setText('(%(ba)s) %(text)s (%(ba)s)' % args) + labels.append(label) + + for label in labels: + d.add(label) + + drawings.append(d) + + return drawings + + + def test1(self): + "Test all different box anchors." + + # Build story. + story = [] + styleSheet = getSampleStyleSheet() + bt = styleSheet['BodyText'] + h1 = styleSheet['Heading1'] + h2 = styleSheet['Heading2'] + h3 = styleSheet['Heading3'] + + story.append(Paragraph('Tests for class Label', h1)) + story.append(Paragraph('Testing box anchors', h2)) + story.append(Paragraph("""This should display "Hello World" labels +written as black text on a yellow box relative to the origin of the crosshair +axes. The labels indicate their relative position being one of the nine +canonical points of a box: sw, se, nw, ne, w, e, n, s or c (standing for +southwest, southeast... and center).""", bt)) + story.append(Spacer(0, 0.5*cm)) + + # Round 1a + story.append(Paragraph('Helvetica 10pt', h3)) + story.append(Spacer(0, 0.5*cm)) + + w, h = drawWidth, drawHeight = 400, 100 + protoLabel = self._makeProtoLabel() + protoLabel.setOrigin(drawWidth/2, drawHeight/2) + protoLabel.textAnchor = 'start' + protoLabel.fontName = 'Helvetica' + protoLabel.fontSize = 10 + drawings = self._makeDrawings(protoLabel) + for d in drawings: + story.append(d) + story.append(Spacer(0, 1*cm)) + + story.append(PageBreak()) + + # Round 1b + story.append(Paragraph('Helvetica 18pt', h3)) + story.append(Spacer(0, 0.5*cm)) + + w, h = drawWidth, drawHeight = 400, 100 + protoLabel = self._makeProtoLabel() + protoLabel.setOrigin(drawWidth/2, drawHeight/2) + protoLabel.textAnchor = 'start' + protoLabel.fontName = 'Helvetica' + protoLabel.fontSize = 18 + drawings = self._makeDrawings(protoLabel) + for d in drawings: + story.append(d) + story.append(Spacer(0, 1*cm)) + + story.append(PageBreak()) + + # Round 1c + story.append(Paragraph('Helvetica 18pt, multi-line', h3)) + story.append(Spacer(0, 0.5*cm)) + + w, h = drawWidth, drawHeight = 400, 100 + protoLabel = self._makeProtoLabel() + protoLabel.setOrigin(drawWidth/2, drawHeight/2) + protoLabel.textAnchor = 'start' + protoLabel.fontName = 'Helvetica' + protoLabel.fontSize = 18 + drawings = self._makeDrawings(protoLabel, text='Hello\nWorld!') + for d in drawings: + story.append(d) + story.append(Spacer(0, 1*cm)) + + story.append(PageBreak()) + + story.append(Paragraph('Testing text (and box) anchors', h2)) + story.append(Paragraph("""This should display labels as before, +but now with a fixes size and showing some effect of setting the +textAnchor attribute.""", bt)) + story.append(Spacer(0, 0.5*cm)) + + # Round 2a + story.append(Paragraph("Helvetica 10pt, textAnchor='start'", h3)) + story.append(Spacer(0, 0.5*cm)) + + w, h = drawWidth, drawHeight = 400, 100 + protoLabel = self._makeProtoLabel() + protoLabel.setOrigin(drawWidth/2, drawHeight/2) + protoLabel.width = 4*cm + protoLabel.height = 1.5*cm + protoLabel.textAnchor = 'start' + protoLabel.fontName = 'Helvetica' + protoLabel.fontSize = 10 + drawings = self._makeDrawings(protoLabel) + for d in drawings: + story.append(d) + story.append(Spacer(0, 1*cm)) + + story.append(PageBreak()) + + # Round 2b + story.append(Paragraph("Helvetica 10pt, textAnchor='middle'", h3)) + story.append(Spacer(0, 0.5*cm)) + + w, h = drawWidth, drawHeight = 400, 100 + protoLabel = self._makeProtoLabel() + protoLabel.setOrigin(drawWidth/2, drawHeight/2) + protoLabel.width = 4*cm + protoLabel.height = 1.5*cm + protoLabel.textAnchor = 'middle' + protoLabel.fontName = 'Helvetica' + protoLabel.fontSize = 10 + drawings = self._makeDrawings(protoLabel) + for d in drawings: + story.append(d) + story.append(Spacer(0, 1*cm)) + + story.append(PageBreak()) + + # Round 2c + story.append(Paragraph("Helvetica 10pt, textAnchor='end'", h3)) + story.append(Spacer(0, 0.5*cm)) + + w, h = drawWidth, drawHeight = 400, 100 + protoLabel = self._makeProtoLabel() + protoLabel.setOrigin(drawWidth/2, drawHeight/2) + protoLabel.width = 4*cm + protoLabel.height = 1.5*cm + protoLabel.textAnchor = 'end' + protoLabel.fontName = 'Helvetica' + protoLabel.fontSize = 10 + drawings = self._makeDrawings(protoLabel) + for d in drawings: + story.append(d) + story.append(Spacer(0, 1*cm)) + + story.append(PageBreak()) + + # Round 2d + story.append(Paragraph("Helvetica 10pt, multi-line, textAnchor='start'", h3)) + story.append(Spacer(0, 0.5*cm)) + + w, h = drawWidth, drawHeight = 400, 100 + protoLabel = self._makeProtoLabel() + protoLabel.setOrigin(drawWidth/2, drawHeight/2) + protoLabel.width = 4*cm + protoLabel.height = 1.5*cm + protoLabel.textAnchor = 'start' + protoLabel.fontName = 'Helvetica' + protoLabel.fontSize = 10 + drawings = self._makeDrawings(protoLabel, text='Hello\nWorld!') + for d in drawings: + story.append(d) + story.append(Spacer(0, 1*cm)) + + story.append(PageBreak()) + + path = outputfile('test_charts_textlabels.pdf') + doc = MyDocTemplate(path) + doc.multiBuild(story) + + +def makeSuite(): + return makeSuiteForClasses(LabelTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_docs_build.py b/bin/reportlab/test/test_docs_build.py new file mode 100644 index 00000000000..ef14a6e4d3b --- /dev/null +++ b/bin/reportlab/test/test_docs_build.py @@ -0,0 +1,51 @@ +"""Tests that all manuals can be built. +""" + + +import os, sys + +from reportlab.test import unittest +from reportlab.test.utils import SecureTestCase, printLocation + + +class ManualTestCase(SecureTestCase): + "Runs all 3 manual-builders from the top." + + def test0(self): + "Test if all manuals buildable from source." + + import reportlab + rlFolder = os.path.dirname(reportlab.__file__) + docsFolder = os.path.join(rlFolder, 'docs') + os.chdir(docsFolder) + + if os.path.isfile('userguide.pdf'): + os.remove('userguide.pdf') + if os.path.isfile('graphguide.pdf'): + os.remove('graphguide.pdf') + if os.path.isfile('reference.pdf'): + os.remove('reference.pdf') + if os.path.isfile('graphics_reference.pdf'): + os.remove('graphics_reference.pdf') + + os.system("%s genAll.py -s" % sys.executable) + + assert os.path.isfile('userguide.pdf'), 'genAll.py failed to generate userguide.pdf!' + assert os.path.isfile('graphguide.pdf'), 'genAll.py failed to generate graphguide.pdf!' + assert os.path.isfile('reference.pdf'), 'genAll.py failed to generate reference.pdf!' + assert os.path.isfile('graphics_reference.pdf'), 'genAll.py failed to generate graphics_reference.pdf!' + + +def makeSuite(): + suite = unittest.TestSuite() + loader = unittest.TestLoader() + if sys.platform[:4] != 'java': + suite.addTest(loader.loadTestsFromTestCase(ManualTestCase)) + + return suite + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_docstrings.py b/bin/reportlab/test/test_docstrings.py new file mode 100644 index 00000000000..db1f2d56e6f --- /dev/null +++ b/bin/reportlab/test/test_docstrings.py @@ -0,0 +1,175 @@ +#!/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/test/test_docstrings.py + +"""This is a test on a package level that find all modules, +classes, methods and functions that do not have a doc string +and lists them in individual log files. + +Currently, methods with leading and trailing double underscores +are skipped. +""" + +import os, sys, glob, string, re +from types import ModuleType, ClassType, MethodType, FunctionType + +import reportlab +from reportlab.test import unittest +from reportlab.test.utils import SecureTestCase, GlobDirectoryWalker, outputfile, printLocation + + +RL_HOME = os.path.dirname(reportlab.__file__) + +def getModuleObjects(folder, rootName, typ, pattern='*.py'): + "Get a list of all objects defined *somewhere* in a package." + + # Define some abbreviations. + find = string.find + split = string.split + replace = string.replace + + objects = [] + lookup = {} + for file in GlobDirectoryWalker(folder, pattern): + folder = os.path.dirname(file) + + if os.path.basename(file) == '__init__.py': + continue + +## if os.path.exists(os.path.join(folder, '__init__.py')): +#### print 'skipping', os.path.join(folder, '__init__.py') +## continue + + sys.path.insert(0, folder) + cwd = os.getcwd() + os.chdir(folder) + + modName = os.path.splitext(os.path.basename(file))[0] + prefix = folder[find(folder, rootName):] + prefix = replace(prefix, os.sep, '.') + mName = prefix + '.' + modName + + try: + module = __import__(mName) + except ImportError: + # Restore sys.path and working directory. + os.chdir(cwd) + del sys.path[0] + continue + + # Get the 'real' (leaf) module + # (__import__ loads only the top-level one). + if find(mName, '.') != -1: + for part in split(mName, '.')[1:]: + module = getattr(module, part) + + # Find the objects in the module's content. + modContentNames = dir(module) + + # Handle modules. + if typ == ModuleType: + if find(module.__name__, 'reportlab') > -1: + objects.append((mName, module)) + continue + + for n in modContentNames: + obj = eval(mName + '.' + n) + # Handle functions and classes. + if typ in (FunctionType, ClassType): + if type(obj) == typ and not lookup.has_key(obj): + if typ == ClassType: + if find(obj.__module__, rootName) != 0: + continue + objects.append((mName, obj)) + lookup[obj] = 1 + # Handle methods. + elif typ == MethodType: + if type(obj) == ClassType: + for m in dir(obj): + a = getattr(obj, m) + if type(a) == typ and not lookup.has_key(a): + if find(a.im_class.__module__, rootName) != 0: + continue + cName = obj.__name__ + objects.append((mName, a)) + lookup[a] = 1 + + # Restore sys.path and working directory. + os.chdir(cwd) + del sys.path[0] + return objects + +class DocstringTestCase(SecureTestCase): + "Testing if objects in the ReportLab package have docstrings." + + def _writeLogFile(self, objType): + "Write log file for different kind of documentable objects." + + cwd = os.getcwd() + objects = getModuleObjects(RL_HOME, 'reportlab', objType) + objects.sort() + os.chdir(cwd) + + expl = {FunctionType:'functions', + ClassType:'classes', + MethodType:'methods', + ModuleType:'modules'}[objType] + + path = outputfile("test_docstrings-%s.log" % expl) + file = open(path, 'w') + file.write('No doc strings found for the following %s below.\n\n' % expl) + p = re.compile('__.+__') + + lines = [] + for name, obj in objects: + if objType == MethodType: + n = obj.__name__ + # Skip names with leading and trailing double underscores. + if p.match(n): + continue + + if objType == FunctionType: + if not obj.__doc__ or len(obj.__doc__) == 0: + lines.append("%s.%s\n" % (name, obj.__name__)) + else: + if not obj.__doc__ or len(obj.__doc__) == 0: + if objType == ClassType: + lines.append("%s.%s\n" % (obj.__module__, obj.__name__)) + elif objType == MethodType: + lines.append("%s.%s\n" % (obj.im_class, obj.__name__)) + else: + lines.append("%s\n" % (obj.__name__)) + + lines.sort() + for line in lines: + file.write(line) + + file.close() + + def test0(self): + "Test if functions have a doc string." + self._writeLogFile(FunctionType) + + def test1(self): + "Test if classes have a doc string." + self._writeLogFile(ClassType) + + def test2(self): + "Test if methods have a doc string." + self._writeLogFile(MethodType) + + def test3(self): + "Test if modules have a doc string." + self._writeLogFile(ModuleType) + +def makeSuite(): + suite = unittest.TestSuite() + loader = unittest.TestLoader() + if sys.platform[:4] != 'java': suite.addTest(loader.loadTestsFromTestCase(DocstringTestCase)) + return suite + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_extra.py b/bin/reportlab/test/test_extra.py new file mode 100644 index 00000000000..e8e7828710a --- /dev/null +++ b/bin/reportlab/test/test_extra.py @@ -0,0 +1,102 @@ +"""This executes tests defined outside the normal test suite. + +See docstring for class ExternalTestCase for more information. +""" + + +import os, string, fnmatch, re, sys + +import reportlab +from reportlab.test import unittest +from reportlab.test.utils import SecureTestCase, printLocation + + +RL_HOME = os.path.dirname(reportlab.__file__) +EXTRA_FILE = 'extra.txt' + + +class ExternalTestCase(SecureTestCase): + """Test case starting cases external to the normal RL suite. + + This test case runs additional test cases defined in external + modules which must also be using the unittest framework. + These additional modules must be defined in a file named + 'extra.txt' which needs to be located in the reportlab/test + folder and contains one path per line. + + The paths of these modules can contain stuff from the Unix + world like '~', '.', '..' and '$HOME' and can have absolute + or relative paths. If they are relative they start from the + reportlab/test folder. + + This is a sample 'extra.txt' file: + + foo.py + ./foo.py + bar/foo.py + ../foo.py + /bar/foo.py + ~/foo.py + ~/bar/foo.py + ~/../foo.py + $HOME/bar/foo.py + """ + + def test0(self): + "Execute external test cases." + + cwd = os.getcwd() + + # look for a file named 'extra.txt' in test directory, + # exit if not found + extraFilename = os.path.join(RL_HOME, 'test', EXTRA_FILE) + if not os.path.exists(extraFilename): + return + + # read module paths from file + extraModulenames = open(extraFilename).readlines() + extraModulenames = map(string.strip, extraModulenames) + + # expand pathnames as much as possible + for f in extraModulenames: + if f == '': + continue + if f[0] == '#': + continue + f = os.path.expanduser(f) + f = os.path.expandvars(f) + f = os.path.abspath(f) + + if os.path.exists(f): + # look for a makeSuite function and execute it if present + folder = os.path.abspath(os.path.dirname(f)) + modname = os.path.splitext(os.path.basename(f))[0] + os.chdir(folder) + sys.path.insert(0, folder) + + module = __import__(modname) # seems to fail sometimes... + if 'makeSuite' in dir(module): + print "running", f + testSuite = module.makeSuite() + unittest.TextTestRunner().run(testSuite) + os.chdir(cwd) + sys.path = sys.path[1:] + + +def makeSuite(): + suite = unittest.TestSuite() + loader = unittest.TestLoader() + if sys.platform[:4] != 'java': + suite.addTest(loader.loadTestsFromTestCase(ExternalTestCase)) + + return suite + + +#noruntests +if __name__ == "__main__": + if len(sys.argv) > 1: + EXTRA_FILE = sys.argv[1] + assert os.path.isfile(EXTRA_FILE), 'file %s not found!' % EXTRA_FILE + # otherwise, extra.txt will be used + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_graphics_charts.py b/bin/reportlab/test/test_graphics_charts.py new file mode 100644 index 00000000000..b4190e91c6b --- /dev/null +++ b/bin/reportlab/test/test_graphics_charts.py @@ -0,0 +1,423 @@ +#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/test/test_graphics_charts.py +""" +Tests for chart class. +""" + +import os, sys, copy +from os.path import join, basename, splitext + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation +from reportlab.lib import colors +from reportlab.lib.units import cm +from reportlab.lib.pagesizes import A4 +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.lib.validators import Auto +from reportlab.pdfgen.canvas import Canvas +from reportlab.graphics.shapes import * +from reportlab.graphics.charts.textlabels import Label +from reportlab.platypus.flowables import Spacer, PageBreak +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.xpreformatted import XPreformatted +from reportlab.platypus.frames import Frame +from reportlab.platypus.doctemplate \ + import PageTemplate, BaseDocTemplate + +from reportlab.graphics.charts.barcharts import VerticalBarChart +from reportlab.graphics.charts.linecharts import HorizontalLineChart +from reportlab.graphics.charts.lineplots import LinePlot +from reportlab.graphics.charts.piecharts import Pie +from reportlab.graphics.charts.legends import Legend +from reportlab.graphics.charts.spider import SpiderChart +from reportlab.graphics.widgets.markers import makeMarker + + + +def myMainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + + #canvas.rect(2.5*cm, 2.5*cm, 15*cm, 25*cm) + canvas.setFont('Times-Roman', 12) + pageNumber = canvas.getPageNumber() + canvas.drawString(10*cm, cm, str(pageNumber)) + + canvas.restoreState() + + +class MyDocTemplate(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) + template = PageTemplate('normal', [frame1], myMainPageFrame) + self.addPageTemplates(template) + + +def sample1bar(data=[(13, 5, 20, 22, 37, 45, 19, 4)]): + drawing = Drawing(400, 200) + + bc = VerticalBarChart() + bc.x = 50 + bc.y = 50 + bc.height = 125 + bc.width = 300 + bc.data = data + + bc.strokeColor = colors.black + + bc.valueAxis.valueMin = 0 + bc.valueAxis.valueMax = 60 + bc.valueAxis.valueStep = 15 + + bc.categoryAxis.labels.boxAnchor = 'ne' + bc.categoryAxis.labels.dx = 8 + bc.categoryAxis.labels.dy = -2 + bc.categoryAxis.labels.angle = 30 + + catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ') + catNames = map(lambda n:n+'-99', catNames) + bc.categoryAxis.categoryNames = catNames + drawing.add(bc) + + return drawing + + +def sample2bar(data=[(13, 5, 20, 22, 37, 45, 19, 4), + (14, 6, 21, 23, 38, 46, 20, 5)]): + return sample1bar(data) + + +def sample1line(data=[(13, 5, 20, 22, 37, 45, 19, 4)]): + drawing = Drawing(400, 200) + + bc = HorizontalLineChart() + bc.x = 50 + bc.y = 50 + bc.height = 125 + bc.width = 300 + bc.data = data + + bc.strokeColor = colors.black + + bc.valueAxis.valueMin = 0 + bc.valueAxis.valueMax = 60 + bc.valueAxis.valueStep = 15 + + bc.categoryAxis.labels.boxAnchor = 'ne' + bc.categoryAxis.labels.dx = 8 + bc.categoryAxis.labels.dy = -2 + bc.categoryAxis.labels.angle = 30 + + catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ') + catNames = map(lambda n:n+'-99', catNames) + bc.categoryAxis.categoryNames = catNames + drawing.add(bc) + + return drawing + + +def sample2line(data=[(13, 5, 20, 22, 37, 45, 19, 4), + (14, 6, 21, 23, 38, 46, 20, 5)]): + return sample1line(data) + + +def sample3(drawing=None): + "Add sample swatches to a diagram." + + d = drawing or Drawing(400, 200) + + swatches = Legend() + swatches.alignment = 'right' + swatches.x = 80 + swatches.y = 160 + swatches.deltax = 60 + swatches.dxTextSpace = 10 + swatches.columnMaximum = 4 + items = [(colors.red, 'before'), (colors.green, 'after')] + swatches.colorNamePairs = items + + d.add(swatches, 'legend') + + return d + + +def sample4pie(): + width = 300 + height = 150 + d = Drawing(width, height) + pc = Pie() + pc.x = 150 + pc.y = 50 + pc.data = [1, 50, 100, 100, 100, 100, 100, 100, 100, 50] + pc.labels = ['0','a','b','c','d','e','f','g','h','i'] + pc.slices.strokeWidth=0.5 + pc.slices[3].popout = 20 + pc.slices[3].strokeWidth = 2 + pc.slices[3].strokeDashArray = [2,2] + pc.slices[3].labelRadius = 1.75 + pc.slices[3].fontColor = colors.red + d.add(pc) + legend = Legend() + legend.x = width-5 + legend.y = height-5 + legend.dx = 20 + legend.dy = 5 + legend.deltax = 0 + legend.boxAnchor = 'nw' + legend.colorNamePairs=Auto(chart=pc) + d.add(legend) + return d + +def autoLegender(i,chart,styleObj,sym='symbol'): + if sym: + setattr(styleObj[0],sym, makeMarker('Diamond',size=6)) + setattr(styleObj[1],sym,makeMarker('Square')) + width = 300 + height = 150 + legend = Legend() + legend.x = width-5 + legend.y = 5 + legend.dx = 20 + legend.dy = 5 + legend.deltay = 0 + legend.boxAnchor = 'se' + if i=='col auto': + legend.colorNamePairs[0]=(Auto(chart=chart),'auto chart=self.chart') + legend.colorNamePairs[1]=(Auto(obj=chart,index=1),'auto chart=self.chart index=1') + elif i=='full auto': + legend.colorNamePairs=Auto(chart=chart) + elif i=='swatch set': + legend.swatchMarker=makeMarker('Circle') + legend.swatchMarker.size = 10 + elif i=='swatch auto': + legend.swatchMarker=Auto(chart=chart) + d = Drawing(width,height) + d.background = Rect(0,0,width,height,strokeWidth=1,strokeColor=colors.red,fillColor=None) + m = makeMarker('Cross') + m.x = width-5 + m.y = 5 + m.fillColor = colors.red + m.strokeColor = colors.yellow + d.add(chart) + d.add(legend) + d.add(m) + return d + +def lpleg(i=None): + chart = LinePlot() + return autoLegender(i,chart,chart.lines) + +def hlcleg(i=None): + chart = HorizontalLineChart() + return autoLegender(i,chart,chart.lines) + +def bcleg(i=None): + chart = VerticalBarChart() + return autoLegender(i,chart,chart.bars,None) + +def pcleg(i=None): + chart = Pie() + return autoLegender(i,chart,chart.slices,None) + +def scleg(i=None): + chart = SpiderChart() + return autoLegender(i,chart,chart.strands,None) + +def plpleg(i=None): + from reportlab.lib.colors import pink, red, green + pie = Pie() + pie.x = 0 + pie.y = 0 + pie.pointerLabelMode='LeftAndRight' + pie.slices.label_boxStrokeColor = red + pie.simpleLabels = 0 + pie.sameRadii = 1 + pie.data = [1, 0.1, 1.7, 4.2,0,0] + pie.labels = ['abcdef', 'b', 'c', 'd','e','fedcba'] + pie.strokeWidth=1 + pie.strokeColor=green + pie.slices.label_pointer_piePad = 6 + pie.width = 160 + pie.direction = 'clockwise' + pie.pointerLabelMode = 'LeftRight' + return autoLegender(i,pie,pie.slices,None) + +def notFail(d): + try: + return d.getContents() + except: + import traceback + traceback.print_exc() + return None + +STORY = [] +styleSheet = getSampleStyleSheet() +bt = styleSheet['BodyText'] +h1 = styleSheet['Heading1'] +h2 = styleSheet['Heading2'] +h3 = styleSheet['Heading3'] +FINISHED = 0 + + +class ChartTestCase(unittest.TestCase): + "Test chart classes." + + def setUp(self): + "Hook method for setting up the test fixture before exercising it." + + global STORY + self.story = STORY + + if self.story == []: + self.story.append(Paragraph('Tests for chart classes', h1)) + + def tearDown(self): + "Hook method for deconstructing the test fixture after testing it." + + if FINISHED: + path=outputfile('test_graphics_charts.pdf') + doc = MyDocTemplate(path) + doc.build(self.story) + + def test0(self): + "Test bar charts." + + story = self.story + story.append(Paragraph('Single data row', h2)) + + story.append(Spacer(0, 0.5*cm)) + drawing = sample1bar() + story.append(drawing) + story.append(Spacer(0, 1*cm)) + + + def test1(self): + "Test bar charts." + + story = self.story + story.append(Paragraph('Double data row', h2)) + + story.append(Spacer(0, 0.5*cm)) + drawing = sample2bar() + story.append(drawing) + story.append(Spacer(0, 1*cm)) + + + def test2(self): + "Test bar charts." + + story = self.story + story.append(Paragraph('Double data row with legend', h2)) + + story.append(Spacer(0, 0.5*cm)) + drawing = sample2bar() + drawing = sample3(drawing) + story.append(drawing) + story.append(Spacer(0, 1*cm)) + + + def test3(self): + "Test line charts." + + story = self.story + story.append(Paragraph('Single data row', h2)) + + story.append(Spacer(0, 0.5*cm)) + drawing = sample1line() + story.append(drawing) + story.append(Spacer(0, 1*cm)) + + + def test4(self): + "Test line charts." + + story = self.story + story.append(Paragraph('Single data row', h2)) + + story.append(Spacer(0, 0.5*cm)) + drawing = sample2line() + story.append(drawing) + story.append(Spacer(0, 1*cm)) + + def test4b(self): + story = self.story + for code in (lpleg, hlcleg, bcleg, pcleg, scleg, plpleg): + code_name = code.func_code.co_name + for i in ('standard', 'col auto', 'full auto', 'swatch set', 'swatch auto'): + d = code(i) + assert notFail(d),'getContents failed for %s %s' % (code_name,i) + story.append(Paragraph('%s %s' % (i,code_name), h2)) + story.append(Spacer(0, 0.5*cm)) + story.append(code(i)) + story.append(Spacer(0, 1*cm)) + + def test5(self): + "Test pie charts." + + story = self.story + story.append(Paragraph('Pie', h2)) + + story.append(Spacer(0, 0.5*cm)) + drawing = sample4pie() + story.append(drawing) + story.append(Spacer(0, 1*cm)) + + def test6(self): + from reportlab.graphics.charts.piecharts import Pie, _makeSideArcDefs, intervalIntersection + L = [] + + def intSum(arcs,A): + s = 0 + for a in A: + for arc in arcs: + i = intervalIntersection(arc[1:],a) + if i: s += i[1] - i[0] + return s + + def subtest(sa,d): + pc = Pie() + pc.direction=d + pc.startAngle=sa + arcs = _makeSideArcDefs(sa,d) + A = [x[1] for x in pc.makeAngles()] + arcsum = sum([a[2]-a[1] for a in arcs]) + isum = intSum(arcs,A) + mi = max([a[2]-a[1] for a in arcs]) + ni = min([a[2]-a[1] for a in arcs]) + l = [] + s = (arcsum-360) + if s>1e-8: l.append('Arc length=%s != 360' % s) + s = abs(isum-360) + if s>1e-8: l.append('interval intersection length=%s != 360' % s) + if mi>360: l.append('max interval intersection length=%s >360' % mi) + if ni<0: l.append('min interval intersection length=%s <0' % ni) + if l: + l.append('sa: %s d: %s' % (sa,d)) + l.append('sidearcs: %s' % str(arcs)) + l.append('Angles: %s' % A) + raise ValueError('piecharts._makeSideArcDefs failure\n%s' % '\n'.join(l)) + + for d in ('anticlockwise','clockwise'): + for sa in (225, 180, 135, 90, 45, 0, -45, -90): + subtest(sa,d) + + # This triggers the document build operation (hackish). + global FINISHED + FINISHED = 1 + +def makeSuite(): + return makeSuiteForClasses(ChartTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_graphics_images.py b/bin/reportlab/test/test_graphics_images.py new file mode 100644 index 00000000000..58ca119a196 --- /dev/null +++ b/bin/reportlab/test/test_graphics_images.py @@ -0,0 +1,91 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +""" +Tests for RLG Image shapes. +""" + +import os + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.graphics.shapes import Image, Drawing +from reportlab.graphics import renderPDF +from reportlab.lib.pagesizes import A4 + + +IMAGES = [] +IMAGENAME = 'cs_logo.gif' +IMAGENAME = 'pythonpowered.gif' + + +class ImageTestCase(unittest.TestCase): + "Test RLG Image shape." + + def __del__(self): + if IMAGES[-1] != None: + return + else: + del IMAGES[-1] + + d = Drawing(A4[0], A4[1]) + for img in IMAGES: + d.add(img) + outPath = outputfile("test_graphics_images.pdf") + renderPDF.drawToFile(d, outPath) #, '') + assert os.path.exists(outPath) == 1 + + + def test0(self): + "Test convert a bitmap file as Image shape into a tmp. PDF file." + + d = Drawing(110, 44) + inPath = IMAGENAME + img = Image(0, 0, 110, 44, inPath) + d.add(img) + IMAGES.append(img) + + + def test1(self): + "Test Image shape, adding it to a PDF page." + + inPath = IMAGENAME + img = Image(0, 0, 110, 44, inPath) + IMAGES.append(img) + + + def test2(self): + "Test scaled Image shape adding it to a PDF page." + + inPath = IMAGENAME + img = Image(0, 0, 110, 44, inPath) + d = Drawing(110, 44) + d.add(img) + d.translate(120, 0) + d.scale(2, 2) + IMAGES.append(d) + + + def test3(self): + "Test rotated Image shape adding it to a PDF page." + + inPath = IMAGENAME + img = Image(0, 0, 110, 44, inPath) + d = Drawing(110, 44) + d.add(img) + d.translate(420, 0) + d.scale(2, 2) + d.rotate(45) + IMAGES.append(d) + + IMAGES.append(None) # used to indicate last test + + +def makeSuite(): + return makeSuiteForClasses(ImageTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_graphics_layout.py b/bin/reportlab/test/test_graphics_layout.py new file mode 100644 index 00000000000..a4f13360c24 --- /dev/null +++ b/bin/reportlab/test/test_graphics_layout.py @@ -0,0 +1,82 @@ +#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/test/test_graphics_layout.py +""" +Tests for getBounds methods of various graphical widgets +""" + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, printLocation + +from reportlab.graphics import shapes +##from reportlab.graphics.charts.barcharts import VerticalBarChart +##from reportlab.graphics.charts.linecharts import HorizontalLineChart +##from reportlab.graphics.charts.piecharts import Pie +##from reportlab.graphics.charts.legends import Legend + +class BoundsTestCase(unittest.TestCase): + def testLine(self): + s = shapes.Line(10,20,30,40) + assert s.getBounds() == (10,20,30,40) + + def testRect(self): + s = shapes.Rect(10,20,30,40) #width, height + assert s.getBounds() == (10,20,40,60) + + def testCircle(self): + s = shapes.Circle(100, 50, 10) + assert s.getBounds() == (90,40,110,60) + + def testEllipse(self): + s = shapes.Ellipse(100, 50, 10, 5) + assert s.getBounds() == (90,45,110,55) + + def testWedge(self): + s = shapes.Wedge(0,0,10,0,90) + assert s.getBounds() == (0,0,10,10), 'expected (0,0,10,10) got %s' % repr(s.getBounds()) + + def testPolygon(self): + points = [0,0,10,30,25,15] + s = shapes.Polygon(points) + assert s.getBounds() == (0,0,25,30) + + s = shapes.PolyLine(points) + assert s.getBounds() == (0,0,25,30) + + def testString(self): + s = shapes.String(0,0,'Hello World', fontName='Courier',fontSize=10) + assert s.getBounds() == (0, -2.0, 66.0, 10) + + def testGroup(self): + g = shapes.Group() + g.add(shapes.Rect(0,0,10,10)) + g.add(shapes.Rect(50,50,10,10)) + assert g.getBounds() == (0,0,60,60) + + g.translate(40,40) + assert g.getBounds() == (40,40,100,100) + + g.translate(-40,-40) + g.rotate(90) + #approx bounds needed, trig functions create an error of 3e-15 + assert map(int, g.getBounds()) == [-60,0,0,60] + + def testWidget(self): + from reportlab.graphics.charts.barcharts import VerticalBarChart + vbc = VerticalBarChart() + vbc.x = 50 + vbc.y = 50 + from reportlab.graphics.widgetbase import Sizer + siz = Sizer() + siz.add(vbc, 'vbc') + assert siz.getBounds()[0:2] <> (0,0) + + +def makeSuite(): + return makeSuiteForClasses(BoundsTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_graphics_speed.py b/bin/reportlab/test/test_graphics_speed.py new file mode 100644 index 00000000000..21fc6f9f47e --- /dev/null +++ b/bin/reportlab/test/test_graphics_speed.py @@ -0,0 +1,86 @@ +#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/test/test_graphics_speed.py +""" +This does a test drawing with lots of things in it, running +with and without attribute checking. +""" + +__version__ = ''' $Id $ ''' + + +import os, sys, time, profile + +import reportlab.rl_config +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation +from reportlab.lib import colors +from reportlab.lib.units import cm +from reportlab.pdfgen.canvas import Canvas +from reportlab.pdfbase.pdfmetrics import stringWidth +from reportlab.platypus import Flowable +from reportlab.graphics.shapes import * +from reportlab.graphics.charts.piecharts import Pie + + +class GraphicsSpeedTestCase(unittest.TestCase): + "Test speed of the graphics rendering process." + + def test0(self, isFast=0): + """Hello World, on a rectangular background. + + The rectangle's fillColor is yellow. + The string's fillColor is red. + """ + reportlab.rl_config.shapeChecking = not isFast + + pdfPath = outputfile('test_graphics_speed_fast.pdf') + c = Canvas(pdfPath) + t0 = time.time() + + d = Drawing(400, 200) + num = 100 + for i in range(num): + pc = Pie() + pc.x = 150 + pc.y = 50 + pc.data = [10,20,30,40,50,60] + pc.labels = ['a','b','c','d','e','f'] + pc.slices.strokeWidth=0.5 + pc.slices[3].popout = 20 + pc.slices[3].strokeWidth = 2 + pc.slices[3].strokeDashArray = [2,2] + pc.slices[3].labelRadius = 1.75 + pc.slices[3].fontColor = colors.red + d.add(pc) + d.drawOn(c, 80, 500) + + t1 = time.time() + + result = 'drew %d pie charts in %0.4f' % (num, t1 - t0) + open(outputfile('test_graphics_speed_test%s.log' % (isFast+1)), 'w').write(result) + + + def test1(self, isFast=1): + "Same as test1(), but with shape checking turned on." + + self.test0(isFast) + + + def test2(self): + "This is a profiled version of test1()." + + fileName = outputfile('test_graphics_speed_profile.log') + # This runs ok, when only this test script is executed, + # but fails, when imported from runAll.py... +## profile.run("t = GraphicsSpeedTestCase('test2')", fileName) + + +def makeSuite(): + return makeSuiteForClasses(GraphicsSpeedTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_hello.py b/bin/reportlab/test/test_hello.py new file mode 100644 index 00000000000..2197f405d06 --- /dev/null +++ b/bin/reportlab/test/test_hello.py @@ -0,0 +1,34 @@ +#!/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/test/test_hello.py +__version__=''' $Id''' +__doc__="""most basic test possible that makes a PDF. + +Useful if you want to test that a really minimal PDF is healthy, +since the output is about the smallest thing we can make.""" + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation +from reportlab.pdfgen.canvas import Canvas + + +class HelloTestCase(unittest.TestCase): + "Simplest test that makes PDF" + + def test(self): + c = Canvas(outputfile('test_hello.pdf')) + c.setAuthor('\xe3\x83\x9b\xe3\x83\x86\xe3\x83\xab\xe3\x83\xbbe\xe3\x83\x91\xe3\x83\xb3\xe3\x83\x95\xe3\x83\xac\xe3\x83\x83\xe3\x83\x88') + c.setFont('Helvetica-Bold', 36) + c.drawString(100,700, 'Hello World') + c.save() + + +def makeSuite(): + return makeSuiteForClasses(HelloTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_images.py b/bin/reportlab/test/test_images.py new file mode 100644 index 00000000000..591b2295480 --- /dev/null +++ b/bin/reportlab/test/test_images.py @@ -0,0 +1,55 @@ +#!/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/test/test_images.py +__version__=''' $Id''' +__doc__="""Tests to do with image handling. + +Most of them make use of test\pythonpowereed.gif.""" +import os,md5 + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, printLocation +from reportlab.lib.utils import ImageReader + + +"""To avoid depending on external stuff, I made a small 5x5 image and +attach its 'file contents' here in several formats. + +The image looks like this, with K=black, R=red, G=green, B=blue, W=white. + + K R G B W + K R G B W + K R G B W + K R G B W + K R G B W + +""" + +sampleRAW = '\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xff\xff\xff' +samplePNG = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x05\x00\x00\x00\x05\x08\x02\x00\x00\x00\x02\r\xb1\xb2\x00\x00\x00:IDATx\x9cb```\xf8\x0f\xc3\xff\xff\xff\x07\x00\x00\x00\xff\xffbb@\x05\x00\x00\x00\x00\xff\xffB\xe7\x03\x00\x00\x00\xff\xffB\xe7\x03\x00\x00\x00\xff\xffB\xe7\x03\x00\x00\x00\xff\xff\x03\x00\x9e\x01\x06\x03\x03\xc4A\xb4\x00\x00\x00\x00IEND\xaeB`\x82' + + +class ReaderTestCase(unittest.TestCase): + "Simplest tests to import images, work under Jython or PIL" + + def test(self): + import reportlab.test + from reportlab.lib.utils import rl_isfile + imageFileName = os.path.dirname(reportlab.test.__file__) + os.sep + 'pythonpowered.gif' + assert rl_isfile(imageFileName), "%s not found!" % imageFileName + + ir = ImageReader(imageFileName) + assert ir.getSize() == (110,44) + pixels = ir.getRGBData() + assert md5.md5(pixels).hexdigest() == '02e000bf3ffcefe9fc9660c95d7e27cf' + + +def makeSuite(): + return makeSuiteForClasses(ReaderTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_invariant.py b/bin/reportlab/test/test_invariant.py new file mode 100644 index 00000000000..d75c7c37e10 --- /dev/null +++ b/bin/reportlab/test/test_invariant.py @@ -0,0 +1,56 @@ +#!/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/test/test_invariant.py +__version__=''' $Id''' +__doc__="""Verfy that if in invariant mode, repeated runs +make identical file. This does NOT test across platforms +or python versions, only a user can do that :-)""" + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation +from reportlab.pdfgen.canvas import Canvas +filename = outputfile('test_invariant.pdf') + +class InvarTestCase(unittest.TestCase): + "Simplest test that makes PDF" + def test(self): + + import os + c = Canvas(filename, invariant=1, pageCompression=0) + c.setFont('Helvetica-Bold', 36) + c.drawString(100,700, 'Hello World') + gif = os.path.join(os.path.dirname(unittest.__file__),'pythonpowered.gif') + c.drawImage(gif,100,600) + c.save() + + raw1 = open(filename, 'rb').read() + + c = Canvas(filename, invariant=1, pageCompression=0) + c.setFont('Helvetica-Bold', 36) + c.drawString(100,700, 'Hello World') + c.drawImage(gif,100,600) + c.save() + + raw2 = open(filename, 'rb').read() + assert raw1 == raw2, 'repeated runs differ!' + +def makeSuite(): + return makeSuiteForClasses(InvarTestCase) + + +#noruntests +if __name__ == "__main__": + # add some diagnostics, useful in invariant tests + import sys, md5, os + verbose = ('-v' in sys.argv) + unittest.TextTestRunner().run(makeSuite()) + if verbose: + #tell us about the file we produced + fileSize = os.stat(filename)[6] + raw = open(filename,'rb').read() + digest = md5.md5(raw).hexdigest() + major, minor = sys.version_info[0:2] + print '%s on %s (Python %d.%d):\n %d bytes, digest %s' % ( + filename,sys.platform, major, minor, fileSize, digest) + printLocation() diff --git a/bin/reportlab/test/test_lib_colors.py b/bin/reportlab/test/test_lib_colors.py new file mode 100644 index 00000000000..37ced998f26 --- /dev/null +++ b/bin/reportlab/test/test_lib_colors.py @@ -0,0 +1,161 @@ +#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/test/test_lib_colors.py +"""Tests for the reportlab.lib.colors module. +""" + + +import os, math + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfgen.canvas import Canvas +import reportlab.pdfgen.canvas +from reportlab.lib import colors +from reportlab.lib.units import inch, cm + + +def framePage(canvas, title): + canvas.setFont('Times-BoldItalic',20) + canvas.drawString(inch, 10.5 * inch, title) + + canvas.setFont('Times-Roman',10) + canvas.drawCentredString(4.135 * inch, 0.75 * inch, + 'Page %d' % canvas.getPageNumber()) + + #draw a border + canvas.setStrokeColorRGB(1,0,0) + canvas.setLineWidth(5) + canvas.line(0.8 * inch, inch, 0.8 * inch, 10.75 * inch) + #reset carefully afterwards + canvas.setLineWidth(1) + canvas.setStrokeColorRGB(0,0,0) + + +class ColorTestCase(unittest.TestCase): + "" + + def test0(self): + "Test color2bw function on all named colors." + + cols = colors.getAllNamedColors().values() + for col in cols: + gray = colors.color2bw(col) + r, g, b = gray.red, gray.green, gray.blue + assert r == g == b + + + def test1(self): + "Test colorDistance function." + + cols = colors.getAllNamedColors().values() + for col in cols: + d = colors.colorDistance(col, col) + assert d == 0 + + + def test2(self): + "Test toColor function on half a dozen ways to say 'red'." + + allRed = [colors.red, [1, 0, 0], (1, 0, 0), + 'red', 'RED', '0xFF0000', '0xff0000'] + + for thing in allRed: + assert colors.toColor(thing) == colors.red + + + def test3(self): + "Test roundtrip RGB to CMYK conversion." + + # Take all colors as test subjects, except 'transparent'. +## rgbCols = colors.getAllNamedColors() +## del rgbCols['transparent'] + rgbCols = colors.getAllNamedColors().items() + + # Make a roundtrip test (RGB > CMYK > RGB). + for name, rgbCol in rgbCols: + r1, g1, b1 = rgbCol.red, rgbCol.green, rgbCol.blue + c, m, y, k = colors.rgb2cmyk(r1, g1, b1) + r2, g2, b2 = colors.cmyk2rgb((c, m, y, k)) + rgbCol2 = colors.Color(r2, g2, b2) + + # Make sure the differences for each RGB component + # isreally small (< power(10, -N)! + N = 16 # max. value found to work on Python2.0 and Win2K. + deltas = map(abs, (r1-r2, g1-g2, b1-b2)) + assert deltas < [math.pow(10, -N)] * 3 + + def test4(self): + "Construct CMYK instances and test round trip conversion" + + rgbCols = colors.getAllNamedColors().items() + + # Make a roundtrip test (RGB > CMYK > RGB). + for name, rgbCol in rgbCols: + r1, g1, b1 = rgbCol.red, rgbCol.green, rgbCol.blue + c, m, y, k = colors.rgb2cmyk(r1, g1, b1) + cmykCol = colors.CMYKColor(c,m,y,k) + r2, g2, b2 = cmykCol.red, cmykCol.green, cmykCol.blue #colors.cmyk2rgb((c, m, y, k)) + rgbCol2 = colors.Color(r2, g2, b2) + + # Make sure the differences for each RGB component + # isreally small (< power(10, -N)! + N = 16 # max. value found to work on Python2.0 and Win2K. + deltas = map(abs, (r1-r2, g1-g2, b1-b2)) + assert deltas < [math.pow(10, -N)] * 3 + + + def test5(self): + "List and display all named colors and their gray equivalents." + + canvas = reportlab.pdfgen.canvas.Canvas(outputfile('test_lib_colors.pdf')) + + #do all named colors + framePage(canvas, 'Color Demo - page %d' % canvas.getPageNumber()) + + all_colors = reportlab.lib.colors.getAllNamedColors().items() + all_colors.sort() # alpha order by name + canvas.setFont('Times-Roman', 10) + text = 'This shows all the named colors in the HTML standard (plus their gray and CMYK equivalents).' + canvas.drawString(72,740, text) + + canvas.drawString(200,725,'Pure RGB') + canvas.drawString(300,725,'B&W Approx') + canvas.drawString(400,725,'CMYK Approx') + + y = 700 + for (name, color) in all_colors: + canvas.setFillColor(colors.black) + canvas.drawString(100, y, name) + canvas.setFillColor(color) + canvas.rect(200, y-10, 80, 30, fill=1) + canvas.setFillColor(colors.color2bw(color)) + canvas.rect(300, y-10, 80, 30, fill=1) + + c, m, yel, k = colors.rgb2cmyk(color.red, color.green, color.blue) + CMYK = colors.CMYKColor(c,m,yel,k) + canvas.setFillColor(CMYK) + canvas.rect(400, y-10, 80, 30, fill=1) + + y = y - 40 + if y < 100: + canvas.showPage() + framePage(canvas, 'Color Demo - page %d' % canvas.getPageNumber()) + canvas.setFont('Times-Roman', 10) + y = 700 + canvas.drawString(200,725,'Pure RGB') + canvas.drawString(300,725,'B&W Approx') + canvas.drawString(400,725,'CMYK Approx') + + canvas.save() + + +def makeSuite(): + return makeSuiteForClasses(ColorTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_lib_sequencer.py b/bin/reportlab/test/test_lib_sequencer.py new file mode 100644 index 00000000000..f44686ce271 --- /dev/null +++ b/bin/reportlab/test/test_lib_sequencer.py @@ -0,0 +1,123 @@ +#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/test/test_lib_sequencer.py +"""Tests for the reportlab.lib.sequencer module. +""" + + +import sys, random + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, printLocation +from reportlab.lib.sequencer import Sequencer + + +class SequencerTestCase(unittest.TestCase): + "Test Sequencer usage." + + def test0(self): + "Test sequencer default counter." + + seq = Sequencer() + msg = 'Initial value is not zero!' + assert seq._this() == 0, msg + + + def test1(self): + "Test incrementing default counter." + + seq = Sequencer() + + for i in range(1, 101): + n = seq.next() + msg = 'Sequence value is not correct!' + assert seq._this() == n, msg + + + def test2(self): + "Test resetting default counter." + + seq = Sequencer() + start = seq._this() + + for i in range(1, 101): + n = seq.next() + + seq.reset() + + msg = 'Sequence value not correctly reset!' + assert seq._this() == start, msg + + + def test3(self): + "Test incrementing dedicated counter." + + seq = Sequencer() + + for i in range(1, 101): + n = seq.next('myCounter1') + msg = 'Sequence value is not correct!' + assert seq._this('myCounter1') == n, msg + + + def test4(self): + "Test resetting dedicated counter." + + seq = Sequencer() + start = seq._this('myCounter1') + + for i in range(1, 101): + n = seq.next('myCounter1') + + seq.reset('myCounter1') + + msg = 'Sequence value not correctly reset!' + assert seq._this('myCounter1') == start, msg + + + def test5(self): + "Test incrementing multiple dedicated counters." + + seq = Sequencer() + startMyCounter0 = seq._this('myCounter0') + startMyCounter1 = seq._this('myCounter1') + + for i in range(1, 101): + n = seq.next('myCounter0') + msg = 'Sequence value is not correct!' + assert seq._this('myCounter0') == n, msg + m = seq.next('myCounter1') + msg = 'Sequence value is not correct!' + assert seq._this('myCounter1') == m, msg + + +## def testRandom(self): +## "Test randomly manipulating multiple dedicated counters." +## +## seq = Sequencer() +## counterNames = ['c0', 'c1', 'c2', 'c3'] +## +## # Init. +## for cn in counterNames: +## setattr(self, cn, seq._this(cn)) +## msg = 'Counter start value is not correct!' +## assert seq._this(cn) == 0, msg +## +## # Increment/decrement. +## for i in range(1, 101): +## n = seq.next('myCounter0') +## msg = 'Sequence value is not correct!' +## assert seq._this('myCounter0') == n, msg +## m = seq.next('myCounter1') +## msg = 'Sequence value is not correct!' +## assert seq._this('myCounter1') == m, msg + + +def makeSuite(): + return makeSuiteForClasses(SequencerTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_lib_utils.py b/bin/reportlab/test/test_lib_utils.py new file mode 100644 index 00000000000..d3e6031356e --- /dev/null +++ b/bin/reportlab/test/test_lib_utils.py @@ -0,0 +1,115 @@ +"""Tests for reportlab.lib.utils +""" +import os +import reportlab +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, printLocation +from reportlab.lib import colors +from reportlab.lib.utils import recursiveImport, recursiveGetAttr, recursiveSetAttr, rl_isfile, \ + isCompactDistro + +class ImporterTestCase(unittest.TestCase): + "Test import utilities" + count = 0 + + def setUp(self): + from time import time + from reportlab.lib.utils import get_rl_tempdir + s = `int(time())` + `self.count` + self.__class__.count += 1 + self._tempdir = get_rl_tempdir('reportlab_test','tmp_%s' % s) + _testmodulename = os.path.join(self._tempdir,'test_module_%s.py' % s) + f = open(_testmodulename,'w') + f.write('__all__=[]\n') + f.close() + self._testmodulename = os.path.splitext(os.path.basename(_testmodulename))[0] + + def tearDown(self): + from shutil import rmtree + rmtree(self._tempdir,1) + + def test1(self): + "try stuff known to be in the path" + m1 = recursiveImport('reportlab.pdfgen.canvas') + import reportlab.pdfgen.canvas + assert m1 == reportlab.pdfgen.canvas + + def test2(self): + "try under a well known directory NOT on the path" + D = os.path.join(os.path.dirname(reportlab.__file__), 'tools','pythonpoint') + fn = os.path.join(D,'stdparser.py') + if rl_isfile(fn) or rl_isfile(fn+'c') or rl_isfile(fn+'o'): + m1 = recursiveImport('stdparser', baseDir=D) + + def test3(self): + "ensure CWD is on the path" + try: + cwd = os.getcwd() + os.chdir(self._tempdir) + m1 = recursiveImport(self._testmodulename) + finally: + os.chdir(cwd) + + def test4(self): + "ensure noCWD removes current dir from path" + try: + cwd = os.getcwd() + os.chdir(self._tempdir) + import sys + try: + del sys.modules[self._testmodulename] + except KeyError: + pass + self.assertRaises(ImportError, + recursiveImport, + self._testmodulename, + noCWD=1) + finally: + os.chdir(cwd) + + def test5(self): + "recursive attribute setting/getting on modules" + import reportlab.lib.units + inch = recursiveGetAttr(reportlab, 'lib.units.inch') + assert inch == 72 + + recursiveSetAttr(reportlab, 'lib.units.cubit', 18*inch) + cubit = recursiveGetAttr(reportlab, 'lib.units.cubit') + assert cubit == 18*inch + + def test6(self): + "recursive attribute setting/getting on drawings" + from reportlab.graphics.charts.barcharts import sampleH1 + drawing = sampleH1() + recursiveSetAttr(drawing, 'barchart.valueAxis.valueMax', 72) + theMax = recursiveGetAttr(drawing, 'barchart.valueAxis.valueMax') + assert theMax == 72 + + def test7(self): + "test open and read of a simple relative file" + from reportlab.lib.utils import open_and_read + b = open_and_read('../docs/images/Edit_Prefs.gif') + + def test8(self): + "test open and read of a relative file: URL" + from reportlab.lib.utils import open_and_read + b = open_and_read('file:../docs/images/Edit_Prefs.gif') + + def test9(self): + "test open and read of an http: URL" + from reportlab.lib.utils import open_and_read + b = open_and_read('http://www.reportlab.com/rsrc/encryption.gif') + + def test10(self): + "test open and read of a simple relative file" + from reportlab.lib.utils import open_and_read, getStringIO + b = getStringIO(open_and_read('../docs/images/Edit_Prefs.gif')) + b = open_and_read(b) + +def makeSuite(): + return makeSuiteForClasses(ImporterTestCase) + + +if __name__ == "__main__": #noruntests + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_lib_validators.py b/bin/reportlab/test/test_lib_validators.py new file mode 100644 index 00000000000..70f74f2ed86 --- /dev/null +++ b/bin/reportlab/test/test_lib_validators.py @@ -0,0 +1,165 @@ +"""Tests (incomplete) for the reportlab.lib.validators module. +""" + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, printLocation +from reportlab.lib import colors +from reportlab.lib import validators + + +class ValidatorTestCase(unittest.TestCase): + "Test validating functions." + + def test0(self): + "Test isBoolean validator." + + msg = "Validation failed for 'boolean' %s!" + + booleans = [0, 1, 'yes','no','true','false'] + badbooleans = ['a',3,-1,()] + isBoolean = validators.isBoolean + for b in booleans: + assert isBoolean(b) == 1, msg % str(b) + for b in badbooleans: + assert isBoolean(b) == 0, msg % str(b) + + + def test1(self): + "Test isNumber validator." + + msg = 'Validation failed for number %s!' + + numbers = [0, 1, 2, -1, -2, 0.0, 0.1, -0.1] + badNumbers = ['aaa',(1,1),(1+1j),colors] + isNumber = validators.isNumber + isListOfNumbers = validators.isListOfNumbers + for n in numbers: + assert isNumber(n) == 1, msg % str(n) + for n in badNumbers: + assert isNumber(n) == 0, msg % str(n) + + msg = 'Validation failed for numbers %s!' + + assert isListOfNumbers(numbers) == 1, msg % str(numbers) + assert isListOfNumbers(badNumbers) == 0, msg % str(badNumbers) + assert isListOfNumbers(numbers+[colors]) == 0, msg % str(numbers+[colors]) + + + def test2(self): + "Test isNumberOrNone validator." + + msg = 'Validation failed for number %s!' + + numbers = [None, 0, 1, 2, -1, -2, 0.0, 0.1, -0.1] #, 2L, -2L] + isNumberOrNone = validators.isNumberOrNone + for n in numbers: + assert isNumberOrNone(n) == 1, msg % str(n) + + + def test4(self): + "Test isString validator." + + msg = 'Validation failed for string %s!' + + strings = ['', '\n', ' ', 'foo', '""'] + badStrings = [1,2.0,None,('a','b')] + isString = validators.isString + isListOfStrings = validators.isListOfStrings + for s in strings: + assert isString(s) == 1, msg % str(s) + for s in badStrings: + assert isString(s) == 0, msg % str(s) + + msg = 'Validation failed for strings %s!' + + assert isListOfStrings(strings) == 1, msg % str(strings) + assert isListOfStrings(badStrings) == 0, msg % str(badStrings) + assert isListOfStrings(strings+[1]) == 0, msg % str(strings+[1]) + + + def test5(self): + "Test isTextAnchor validator." + + msg = 'Validation failed for text anchor %s!' + + strings = ['start', 'middle', 'end'] + isTextAnchor = validators.isTextAnchor + for s in strings: + assert isTextAnchor(s) == 1, msg % s + + """ + def isListOfNumbersOrNone(x): + def isListOfShapes(x): + def isListOfStrings(x): + def isListOfStringsOrNone(x): + def isTransform(x): + def isColor(x): + def isColorOrNone(x): + def isValidChild(x): + class OneOf: + class SequenceOf: + """ + + + def test6(self): + "Test OneOf validator." + + msg = 'Validation failed for OneOf %s!' + + choices = ('clockwise', 'anticlockwise') + OneOf = validators.OneOf(choices) + for c in choices: + assert OneOf(c) == 1, msg % c + for c in ('a', 'b', 'c'): + assert OneOf(c) == 0, msg % c + + OneOf = validators.OneOf('clockwise', 'anticlockwise') + for c in choices: + assert OneOf(c) == 1, msg % c + for c in ('a', 'b', 'c'): + assert OneOf(c) == 0, msg % c + + try: + validators.OneOf(choices,'bongo') + raise AssertionError, "OneOf failed to detect bad arguments" + except ValueError: + pass + + + def test7(self): + "Test isInt validator" + + msg = 'Validation failed for isInt %s!' + + isInt = validators.isInt + for c in (1,2,-3,0,'-4','4'): + assert isInt(c), msg % str(c) + + for c in (1.2,0.0,-3.0,'-4.0','4.4','AAAA'): + assert not isInt(c), msg % str(c) + + + def test8(self): + "test Sequence of validator" + + msg = 'Validation failed for SequenceOf %s!' + + v=validators.SequenceOf(validators.OneOf(('eps','pdf','png','gif','jpg','tif')),lo=1,hi=3,emptyOK=0) + for c in (['png'],('eps',),('eps','pdf')): + assert v(c), msg % str(c) + v._lo = 2 + for c in ([],(),('eps'),('eps','pdf','a'),['eps','pdf','png','gif']): + assert not v(c), msg % str(c) + v._emptyOK=1 + for c in ([],(),('eps','pdf')): + assert v(c), msg % str(c) + + +def makeSuite(): + return makeSuiteForClasses(ValidatorTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_multibyte_chs.py b/bin/reportlab/test/test_multibyte_chs.py new file mode 100644 index 00000000000..59dde43d459 --- /dev/null +++ b/bin/reportlab/test/test_multibyte_chs.py @@ -0,0 +1,84 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history www.reportlab.co.uk/rl-cgi/viewcvs.cgi/rlextra/rlj/jpsupport.py +# Temporary japanese support for ReportLab. +""" +The code in this module will disappear any day now and be replaced +by classes in reportlab.pdfbase.cidfonts +""" + + +import string, os +import codecs +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfgen.canvas import Canvas +from reportlab.lib import colors +from reportlab.lib.codecharts import KutenRowCodeChart, hBoxText + +global VERBOSE +VERBOSE = 0 + + +class CHSFontTests(unittest.TestCase): + + def test0(self): + "A basic document drawing some strings" + + # if they do not have the Japanese font files, go away quietly + from reportlab.pdfbase.cidfonts import UnicodeCIDFont, findCMapFile + + + pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light')) + + c = Canvas(outputfile('test_multibyte_chs.pdf')) + c.setFont('Helvetica', 30) + c.drawString(100,700, 'Simplified Chinese Font Support') + + + c.setFont('Helvetica', 10) + c.drawString(100,680, 'Short sample: "China - Zhang Ziyi" (famous actress)') + # the two typefaces + + hBoxText(u'\u4e2d\u56fd - \u7ae0\u5b50\u6021', + c, + 100, + 660, + 'STSong-Light', + ) + + + c.setFont('Helvetica',10) + c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber()) + c.showPage() + +## # full kuten chart in EUC +## c.setFont('Helvetica', 18) +## c.drawString(72,750, 'Characters available in GB 2312-80, EUC encoding') +## y = 600 +## enc = 'GB_EUC_H' +## for row in range(1, 95): +## KutenRowCodeChart(row, 'STSong-Light',enc).drawOn(c, 72, y) +## y = y - 125 +## if y < 50: +## c.setFont('Helvetica',10) +## c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber()) +## c.showPage() +## y = 700 +## + c.save() + if VERBOSE: + print 'saved '+outputfile('test_multibyte_chs.pdf') + + +def makeSuite(): + return makeSuiteForClasses(CHSFontTests) + + +#noruntests +if __name__ == "__main__": + VERBOSE = 1 + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_multibyte_cht.py b/bin/reportlab/test/test_multibyte_cht.py new file mode 100644 index 00000000000..2191d6e06ec --- /dev/null +++ b/bin/reportlab/test/test_multibyte_cht.py @@ -0,0 +1,131 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history www.reportlab.co.uk/rl-cgi/viewcvs.cgi/rlextra/rlj/jpsupport.py +# Temporary japanese support for ReportLab. +""" +Test of traditional Chinese (as written in Taiwan) +""" + + +import string, os + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfgen.canvas import Canvas +from reportlab.lib import colors +from reportlab.lib.codecharts import Big5CodeChart, hBoxText + +global VERBOSE +VERBOSE = 0 + + +class CHTFontTests(unittest.TestCase): + + def hDraw(self, c, msg, fnt, x, y): + "Helper - draws it with a box around" + c.setFont(fnt, 16, 16) + c.drawString(x, y, msg) + c.rect(x,y,pdfmetrics.stringWidth(msg, fnt, 16),16,stroke=1,fill=0) + + + def test0(self): + "A basic document drawing some strings" + + # if they do not have the Japanese font files, go away quietly + from reportlab.pdfbase.cidfonts import UnicodeCIDFont, findCMapFile + + +## enc = 'ETenms-B5-H' +## try: +## findCMapFile(enc) +## except: +## #they don't have the font pack, return silently +## print 'CMap not found' +## return + pdfmetrics.registerFont(UnicodeCIDFont('MSung-Light')) + + c = Canvas(outputfile('test_multibyte_cht.pdf')) + c.setFont('Helvetica', 24) + c.drawString(100,700, 'Traditional Chinese Font Support') + c.setFont('Helvetica', 10) + c.drawString(100,680, 'Short sample: "Taiwan - Ang Lee" (movie director)') + + hBoxText(u'\u81fa\u7063 - \u674e\u5b89' , c, 100, 600, 'MSung-Light') + + +## #hBoxText(message3 + ' MHei-Medium', c, 100, 580, 'MHei-Medium', enc) +## +## +## +## c.setFont('Helvetica', 10) +## tx = c.beginText(100, 500) +## tx.textLines(""" +## This test document shows Traditional Chinese output from Reportlab PDF Library. +## You may use one Chinese font, MSung-Light, and a number of different +## encodings. +## +## The available encoding names (with comments from the PDF specification) are: +## encodings_cht = [ +## 'B5pc-H', # Macintosh, Big Five character set, Big Five encoding, +## # Script Manager code 2 +## 'B5pc-V', # Vertical version of B5pc-H +## 'ETen-B5-H', # Microsoft Code Page 950 (lfCharSet 0x88), Big Five +## # character set with ETen extensions +## 'ETen-B5-V', # Vertical version of ETen-B5-H +## 'ETenms-B5-H', # Microsoft Code Page 950 (lfCharSet 0x88), Big Five +## # character set with ETen extensions; this uses proportional +## # forms for half-width Latin characters. +## 'ETenms-B5-V', # Vertical version of ETenms-B5-H +## 'CNS-EUC-H', # CNS 11643-1992 character set, EUC-TW encoding +## 'CNS-EUC-V', # Vertical version of CNS-EUC-H +## 'UniCNS-UCS2-H', # Unicode (UCS-2) encoding for the Adobe-CNS1 +## # character collection +## 'UniCNS-UCS2-V' # Vertical version of UniCNS-UCS2-H. +## ] +## +## The next 32 pages show the complete character set available in the encoding +## "ETen-B5-H". This is Big5 with the ETen extensions. ETen extensions are the +## most common extension to Big5 and include circled and roman numbers, Japanese +## hiragana and katakana, Cyrillic and fractions in rows C6-C8; and 7 extra characters +## and some line drawing characters in row F9. +## """) +## c.drawText(tx) +## c.setFont('Helvetica',10) +## c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber()) +## +## c.showPage() +## +## # full Big5 code page +## c.setFont('Helvetica', 18) +## c.drawString(72,750, 'Characters available in Big 5') +## y = 500 +## for row in range(0xA1,0xFF): +## cc = Big5CodeChart(row, 'MSung-Light',enc) +## cc.charsPerRow = 16 +## cc.rows = 10 +## cc.codePoints = 160 +## cc.drawOn(c, 72, y) +## y = y - cc.height - 25 +## if y < 50: +## c.setFont('Helvetica',10) +## c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber()) +## c.showPage() +## y = 600 +## +## + c.save() + if VERBOSE: + print 'saved '+outputfile('test_multibyte_cht.pdf') + + +def makeSuite(): + return makeSuiteForClasses(CHTFontTests) + + +#noruntests +if __name__ == "__main__": + VERBOSE = 1 + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_multibyte_jpn.py b/bin/reportlab/test/test_multibyte_jpn.py new file mode 100644 index 00000000000..c973bc441c0 --- /dev/null +++ b/bin/reportlab/test/test_multibyte_jpn.py @@ -0,0 +1,460 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history www.reportlab.co.uk/rl-cgi/viewcvs.cgi/rlextra/rlj/jpsupport.py +# Temporary japanese support for ReportLab. +""" +The code in this module will disappear any day now and be replaced +by classes in reportlab.pdfbase.cidfonts +""" + + +import string, os + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfgen.canvas import Canvas +from reportlab.lib import colors +from reportlab.lib.codecharts import KutenRowCodeChart +from reportlab.pdfbase.cidfonts import CIDFont, findCMapFile, UnicodeCIDFont +global VERBOSE +VERBOSE = 0 + + +class JapaneseFontTests(unittest.TestCase): + + def hDraw(self, c, msg, fnt, x, y): + "Helper - draws it with a box around" + c.setFont(fnt, 16, 16) + font = pdfmetrics.getFont(fnt) + c.drawString(x, y, msg) + width = font.stringWidth(msg, 16) + c.rect(x,y,width,16,stroke=1,fill=0) + + def test0(self): + "A basic document drawing some strings" + +## # if they do not have the Japanese font files, go away quietly +## try: +## from reportlab.pdfbase.cidfonts import CIDFont, findCMapFile, UnicodeCIDFont +## findCMapFile('90ms-RKSJ-H') +## findCMapFile('90msp-RKSJ-H') +## findCMapFile('UniJIS-UCS2-H') +## findCMapFile('EUC-H') +## except: +## #don't have the font pack. return silently +## return +## +## pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','90ms-RKSJ-H')) +## pdfmetrics.registerFont(CIDFont('HeiseiKakuGo-W5','90ms-RKSJ-H')) + + c = Canvas(outputfile('test_multibyte_jpn.pdf')) + c.setFont('Helvetica', 30) + c.drawString(100,700, 'Japanese Font Support') + + c.setStrokeColor(colors.red) + +## # the two typefaces +## c.setFont('HeiseiMin-W3-90ms-RKSJ-H', 16) +## # this says "This is HeiseiMincho" in shift-JIS. Not all our readers +## # have a Japanese PC, so I escaped it. On a Japanese-capable +## # system, print the string to see Kanji +## message1 = '\202\261\202\352\202\315\225\275\220\254\226\276\222\251\202\305\202\267\201B' +## c.drawString(100, 675, message1) +## wid = pdfmetrics.stringWidth(message1, 'HeiseiMin-W3-90ms-RKSJ-H', 16) +## c.rect(100,675,wid,16,stroke=1,fill=0) +## +## c.setFont('HeiseiKakuGo-W5-90ms-RKSJ-H', 16) +## # this says "This is HeiseiKakugo" in shift-JIS +## message2 = '\202\261\202\352\202\315\225\275\220\254\212p\203S\203V\203b\203N\202\305\202\267\201B' +## c.drawString(100, 650, message2) +## wid = pdfmetrics.stringWidth(message2, 'HeiseiKakuGo-W5-90ms-RKSJ-H', 16) +## c.rect(100,650,wid,16,stroke=1,fill=0) +## +## +## +## self.hDraw(c, '\223\214\213\236 says Tokyo in Shift-JIS', 'HeiseiMin-W3-90ms-RKSJ-H', 100, 600) +## +## +## pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','90msp-RKSJ-H')) +## self.hDraw(c, '\223\214\213\236, but in proportional Shift-JIS.', 'HeiseiMin-W3-90msp-RKSJ-H', 100, 575) +## +## pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','EUC-H')) +## self.hDraw(c, '\xC5\xEC\xB5\xFE says Tokyo in EUC', 'HeiseiMin-W3-EUC-H', 100, 550) +## +## #this is super-slow until we do encoding caching. +## pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','UniJIS-UCS2-H')) +## +## def asciiToUCS2(text): +## s = '' +## for ch in text: +## s = s + chr(0) + ch +## return s +## msg = '\x67\x71\x4E\xAC' + asciiToUCS2(' says Tokyo in UTF16') +## self.hDraw(c, msg,'HeiseiMin-W3-UniJIS-UCS2-H', 100, 525) + + #unicode font automatically supplies the encoding + pdfmetrics.registerFont(UnicodeCIDFont('HeiseiMin-W3')) + + + msg = u'\u6771\u4EAC : Unicode font, unicode input' + self.hDraw(c, msg, 'HeiseiMin-W3', 100, 500) + + msg = u'\u6771\u4EAC : Unicode font, utf8 input'.encode('utf8') + self.hDraw(c, msg, 'HeiseiMin-W3', 100, 475) + + +## # now try verticals +## pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','90ms-RKSJ-V')) +## c.setFont('HeiseiMin-W3-90ms-RKSJ-V', 16) +## c.drawString(400, 650, '\223\214\213\236 vertical Shift-JIS') +## height = c.stringWidth('\223\214\213\236 vertical Shift-JIS', 'HeiseiMin-W3-90ms-RKSJ-V', 16) +## c.rect(400-8,650,16,-height) +## +## pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','EUC-V')) +## c.setFont('HeiseiMin-W3-EUC-V', 16) +## c.drawString(425, 650, '\xC5\xEC\xB5\xFE vertical EUC') +## height = c.stringWidth('\xC5\xEC\xB5\xFE vertical EUC', 'HeiseiMin-W3-EUC-V', 16) +## c.rect(425-8,650,16,-height) +## + + + from reportlab.platypus.paragraph import Paragraph + from reportlab.lib.styles import ParagraphStyle + jStyle = ParagraphStyle('jtext', + fontName='HeiseiMin-W3', + fontSize=12, + wordWrap="CJK" + ) + + gatwickText = '\xe3\x82\xac\xe3\x83\x88\xe3\x82\xa6\xe3\x82\xa3\xe3\x83\x83\xe3\x82\xaf\xe7\xa9\xba\xe6\xb8\xaf\xe3\x81\xa8\xe9\x80\xa3\xe7\xb5\xa1\xe9\x80\x9a\xe8\xb7\xaf\xe3\x81\xa7\xe7\x9b\xb4\xe7\xb5\x90\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3\x82\x8b\xe5\x94\xaf\xe4\xb8\x80\xe3\x81\xae\xe3\x83\x9b\xe3\x83\x86\xe3\x83\xab\xe3\x81\xa7\xe3\x81\x82\xe3\x82\x8b\xe5\xbd\x93\xe3\x83\x9b\xe3\x83\x86\xe3\x83\xab\xe3\x81\xaf\xe3\x80\x81\xe8\xa1\x97\xe3\x81\xae\xe4\xb8\xad\xe5\xbf\x83\xe9\x83\xa8\xe3\x81\x8b\xe3\x82\x8930\xe5\x88\x86\xe3\x81\xae\xe5\xa0\xb4\xe6\x89\x80\xe3\x81\xab\xe3\x81\x94\xe3\x81\x96\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe5\x85\xa8\xe5\xae\xa2\xe5\xae\xa4\xe3\x81\xab\xe9\xab\x98\xe9\x80\x9f\xe3\x82\xa4\xe3\x83\xb3\xe3\x82\xbf\xe3\x83\xbc\xe3\x83\x8d\xe3\x83\x83\xe3\x83\x88\xe7\x92\xb0\xe5\xa2\x83\xe3\x82\x92\xe5\xae\x8c\xe5\x82\x99\xe3\x81\x97\xe3\x81\xa6\xe3\x81\x8a\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe3\x83\x95\xe3\x82\xa1\xe3\x83\x9f\xe3\x83\xaa\xe3\x83\xbc\xe3\x83\xab\xe3\x83\xbc\xe3\x83\xa0\xe3\x81\xaf5\xe5\x90\x8d\xe6\xa7\x98\xe3\x81\xbe\xe3\x81\xa7\xe3\x81\x8a\xe6\xb3\x8a\xe3\x82\x8a\xe3\x81\x84\xe3\x81\x9f\xe3\x81\xa0\xe3\x81\x91\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe3\x81\xbe\xe3\x81\x9f\xe3\x80\x81\xe3\x82\xa8\xe3\x82\xb0\xe3\x82\xbc\xe3\x82\xaf\xe3\x83\x86\xe3\x82\xa3\xe3\x83\x96\xe3\x83\xab\xe3\x83\xbc\xe3\x83\xa0\xe3\x81\xae\xe3\x81\x8a\xe5\xae\xa2\xe6\xa7\x98\xe3\x81\xaf\xe3\x80\x81\xe3\x82\xa8\xe3\x82\xb0\xe3\x82\xbc\xe3\x82\xaf\xe3\x83\x86\xe3\x82\xa3\xe3\x83\x96\xe3\x83\xa9\xe3\x82\xa6\xe3\x83\xb3\xe3\x82\xb8\xe3\x82\x92\xe3\x81\x94\xe5\x88\xa9\xe7\x94\xa8\xe3\x81\x84\xe3\x81\x9f\xe3\x81\xa0\xe3\x81\x91\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82\xe4\xba\x8b\xe5\x89\x8d\xe3\x81\xab\xe3\x81\x94\xe4\xba\x88\xe7\xb4\x84\xe3\x81\x84\xe3\x81\x9f\xe3\x81\xa0\xe3\x81\x91\xe3\x82\x8b\xe3\x82\xbf\xe3\x82\xa4\xe3\x83\xa0\xe3\x83\x88\xe3\x82\xa5\xe3\x83\x95\xe3\x83\xa9\xe3\x82\xa4\xe3\x83\xbb\xe3\x83\x91\xe3\x83\x83\xe3\x82\xb1\xe3\x83\xbc\xe3\x82\xb8\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x81\xe7\xa9\xba\xe6\xb8\xaf\xe3\x81\xae\xe9\xa7\x90\xe8\xbb\x8a\xe6\x96\x99\xe9\x87\x91\xe3\x81\x8c\xe5\x90\xab\xe3\x81\xbe\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x8a\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82' + + c.setFont('HeiseiMin-W3', 12) +## from reportlab.lib.textsplit import wordSplit +## y = 400 +## splat = wordSplit(gatwickText, 250, 'HeiseiMin-W3', 12, encoding='utf8') +## for (line, extraSpace) in splat: +## c.drawString(100,y,line) +## y -= 14 + jPara = Paragraph(gatwickText, jStyle) + jPara.wrap(250, 200) + #from pprint import pprint as pp + #pp(jPara.blPara) + jPara.drawOn(c, 100, 250) + + c.setFillColor(colors.purple) + tx = c.beginText(100, 200) + tx.setFont('Helvetica', 12) + tx.textLines("""This document shows sample output in Japanese + from the Reportlab PDF library. This page shows the two fonts + available and tests our ability to measure the width of glyphs + in both horizontal and vertical writing, with proportional and + fixed-width characters. The red boxes should be the same width + (or height) as the character strings they surround. + The next pages show more samples and information. + """) + c.drawText(tx) + c.setFont('Helvetica',10) + c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber()) + + + + c.showPage() + + c.setFont('Helvetica', 30) + c.drawString(100,700, 'Japanese TrueType Font Support') + msg = u'\u6771\u4EAC : Unicode font, utf8 input'.encode('utf8') + from reportlab.pdfbase.ttfonts import TTFont + try: + msmincho = TTFont('MS Mincho','msmincho.ttc',subfontIndex=0) + fn = ' file=msmincho.ttc subfont 0' + except: + try: + msmincho = TTFont('MS Mincho','msmincho.ttf') + fn = 'file=msmincho.ttf' + except: + msmincho = None + if msmincho is None: + c.drawString(100,600, 'Cannot find msmincho.ttf or msmincho.ttc') + else: + pdfmetrics.registerFont(msmincho) + c.setFont('MS Mincho', 30) + c.drawString(100,600, msg+fn) + if fn.endswith('0'): + try: + msmincho1 = TTFont('MS Mincho 1','msmincho.ttc',subfontIndex=1) + pdfmetrics.registerFont(msmincho1) + fn = ' file=msmincho.ttc subfont 1' + c.setFont('MS Mincho 1',30) + c.drawString(100,500,msg+fn) + except: + c.setFont('Helvetica',30) + c.drawString(100,500,msg+fn) + + c.showPage() + + # realistic text sample +## sample = """Adobe Acrobat +##\x83h\x83L\x83\x85\x83\x81\x83\x93\x83g\x82\xaa\x8aJ\x82\xa9\x82\xc8\x82\xad\x82\xc4\x8d\xa2\x82\xc1\x82\xbd\x82\xb1\x82\xc6\x82\xcd +##\x82\xa0\x82\xe8\x82\xdc\x82\xb9\x82\xf1\x82\xa9\x81B\x8e\x96\x8b\xc6\x8cv\x89\xe6\x8f\x91\x81A\x89c\x8b\xc6\x83\x8c\x83|\x81[\x83g +##\x81A\x83J\x83^\x83\x8d\x83O\x82\xe2\x83p\x83\x93\x83t\x83\x8c\x83b\x83g\x82\xc8\x82\xc7\x90\xa7\x8d\xec\x95\xa8\x82\xcc\x8e\xed +##\x97\xde\x82\xc9\x82\xa9\x82\xa9\x82\xed\x82\xe7\x82\xb8\x81A +##\x83h\x83L\x83\x85\x83\x81\x83\x93\x83g\x82\xcdAdobe® Acrobat® 5.0\x82\xf0\x8eg\x82\xc1\x82\xc4Adobe PDF\x81iPortable Document +##Format\x81j\x83t\x83@\x83C\x83\x8b\x82\xc9\x95\xcf\x8a\xb7\x82\xb5\x82\xdc\x82\xb5\x82\xe5\x82\xa4\x81B\x96\xb3\x8f\x9e\x94z\x95z\x82\xcc +##Adobe Acrobat Reader\x82\xf0\x8eg\x82\xa6\x82\xce\x81A\x83n\x81[\x83h\x83E\x83F\x83A\x81A\x83\\\x83t\x83g\x83E\x83F\x83A\x82\xc9\x82\xa9 +##\x82\xa9\x82\xed\x82\xe7\x82\xb8\x81A\x92N\x82\xc5\x82\xe0\x82\xa0\x82\xc8\x82\xbd\x82\xcc\x83h\x83L\x83\x85\x83\x81\x83\x93\x83g\x82\xf0 +##\x83I\x83\x8a\x83W\x83i\x83\x8b\x82\xcc\x91\xcc\x8d\xd9\x82\xc5\x8aJ\x82\xad\x82\xb1\x82\xc6\x82\xaa\x82\xc5\x82\xab\x82\xdc\x82\xb7\x81B +##\x82\xa0\x82\xc8\x82\xbd\x82\xcc\x88\xd3\x90}\x82\xb5\x82\xbd\x82\xc6\x82\xa8\x82\xe8\x82\xc9\x8f\xee\x95\xf1\x82\xf0\x93`\x82\xa6\x82\xe9 +##\x82\xb1\x82\xc6\x82\xaa\x82\xc5\x82\xab\x82\xdc\x82\xb7\x81B +##\x82\xb3\x82\xe7\x82\xc9\x81AAdobe Acrobat 5.0\x82\xc5\x82\xcd\x81AWeb\x83u\x83\x89\x83E\x83U\x82\xa9\x82\xe7\x83R\x83\x81\x83\x93\x83g\x82\xe2 +##\x83}\x81[\x83N\x83A\x83b\x83v\x82\xf0\x8f\x91\x82\xab\x8d\x9e\x82\xf1\x82\xbe\x82\xe8\x81A\x93d\x8eq\x8f\x90\x96\xbc\x82\xf0\x8f\x91\x82\xab +##\x8d\x9e\x82\xdd\x81A\x8c\xb4\x96{\x82\xc6\x82\xb5\x82\xc4\x83\x8d\x81[\x83J\x83\x8b\x82\xc9\x95\xdb\x91\xb6\x82\xb7\x82\xe9\x82\xb1\x82\xc6\x82\xe0\x89\xc2\x94\\\x82\xc5\x82\xb7\x81B +##\x8a\xe9\x8b\xc6\x93\xe0\x82\xa0\x82\xe9\x82\xa2\x82\xcd\x8a\xe9\x8b\xc6\x82\xcc\x98g\x82\xf0\x92\xb4\x82\xa6\x82\xc4\x83`\x81[\x83\x80\x82\xc5 +##\x82\xcc\x83h\x83L\x83\x85\x83\x81\x83\x93\x83g\x83\x8f\x81[\x83N\x82\xcc\x90\xb6\x8eY\x90\xab\x82\xf0\x8c\xfc\x8f\xe3\x82\xb3\x82\xb9\x82\xe9\x82\xb1\x82\xc6\x82\xaa\x82\xc5\x82\xab\x82\xdc\x82\xb7\x81B +## +##Adobe Acrobat 5.0\x82\xc5\x8d\xec\x90\xac\x82\xb5\x82\xbdAdobe PDF\x82\xcd\x81A(Acrobat 5.0\x82\xc5\x82\xcc\x82\xdd\x83T\x83|\x81[\x83g +##\x82\xb5\x82\xc4\x82\xa2\x82\xe9\x88\xc3\x8d\x86\x89\xbb\x90\xdd\x92\xe8\x82\xf0\x8f\x9c\x82\xa2\x82\xc4\x82\xcd)\x8f]\x97\x88\x82\xdc +##\x82\xc5\x82\xcc\x83o\x81[\x83W\x83\x87\x83\x93(3\x82\xa8\x82\xe6\x82\xd1\x82S)\x82\xccAcrobat Reader\x82\xc5\x82\xe0\x8aJ\x82\xad +##\x82\xb1\x82\xc6\x82\xaa\x82\xc5\x82\xab\x82\xdc\x82\xb7\x81B\x8f\xee\x95\xf1\x8b\xa4\x97L\x82\xcc\x83c\x81[\x83\x8b\x82\xc6\x82\xb5 +##\x82\xc4\x81A\x82\xb3\x82\xe7\x82\xc9\x90i\x95\xe0\x82\xb5\x82\xbdAdobe Acrobat 5.0\x82\xf0\x81A\x8f]\x97\x88\x82\xcc\x8a\xc2\x8b\xab +##\x82\xc5\x82\xe0\x88\xc0\x90S\x82\xb5\x82\xc4\x82\xb2\x97\x98\x97p\x82\xa2\x82\xbd\x82\xbe\x82\xaf\x82\xdc\x82\xb7\x81B +## +##\x96{\x90\xbb\x95i\x82\xf0\x83l\x83b\x83g\x83\x8f\x81[\x83N\x82\xc8\x82\xc7\x82\xf0\x89\xee\x82\xb5\x82\xc4\x92\xbc\x90\xda\x82\xa0\x82\xe9 +##\x82\xa2\x82\xcd\x8a\xd4\x90\xda\x82\xc9\x95\xa1\x90\x94\x82\xcc\x92[\x96\x96\x82\xa9\x82\xe7\x8eg\x97p\x82\xb7\x82\xe9\x8f\xea\x8d\x87\x81A +##\x82\xbb\x82\xcc\x92[\x96\x96\x82\xc6\x93\xaf\x90\x94\x82\xcc\x83\x89\x83C\x83Z\x83\x93\x83X\x82\xf0\x82\xb2\x8dw\x93\xfc\x82\xad\x82\xbe +##\x82\xb3\x82\xa2\x81B\x96{\x90\xbb\x95i\x82\xcd\x83N\x83\x89\x83C\x83A\x83\x93\x83g\x97p\x83\\\x83t\x83g\x83E\x83F\x83A\x82\xc5\x82\xa0\x82\xe8 +##\x81A\x83T\x81[\x83o\x97p\x83\\\x83t\x83g\x83E\x83F\x83A\x82\xc6\x82\xb5\x82\xc4\x82\xa8\x8eg\x82\xa2\x82\xa2\x82\xbd\x82\xbe\x82\xad\x82\xb1\x82\xc6 +##\x82\xcd\x81A\x8f\xe3\x8bL\x95\xfb\x96@\x82\xc9\x82\xe6\x82\xe9\x88\xc8\x8aO\x81A\x8b\x96\x91\xf8\x82\xb3\x82\xea\x82\xc4\x82\xa2\x82\xdc\x82\xb9 +##\x82\xf1\x81B\x95\xa1\x90\x94\x82\xcc\x83\x89\x83C\x83Z\x83\x93\x83X\x82\xf0\x82\xb2\x8dw\x93\xfc\x82\xb3\x82\xea\x82\xe9\x8f\xea\x8d\x87\x82\xc9 +##\x82\xcd\x83\x89\x83C\x83Z\x83\x93\x83X\x83v\x83\x8d\x83O\x83\x89\x83\x80\x82\xf0\x82\xb2\x97\x98\x97p\x82\xc9\x82\xc8\x82\xe9\x82\xc6\x82\xa8\x93\xbe\x82\xc5\x82\xb7\x81B +## +## +##\x81y\x82\xa8\x92m\x82\xe7\x82\xb9\x81zMicrosoft Office XP\x82\xa9\x82\xe7PDF\x82\xf0\x8d\xec\x90\xac\x82\xb7\x82\xe9\x82\xc9\x82\xcd +##""" +## c.setFont('Helvetica', 24) +## c.drawString(100,750, "Sample text from Adobe's web site") +## tx = c.beginText(100,700) +## tx.setFont('Helvetica', 10) +## tx.textLine('Note: line wrapping has not been preserved and some lines may be wrapped in mid-word.') +## tx.textLine('We are just testing that we see Japanese and not random characters!') +## tx.setFont('HeiseiMin-W3-90ms-RKSJ-H',6) +## tx.textLines(sample) +## tx.setFont('Helvetica', 8) +## tx.textLine() +## tx.textLine() +## tx.textLines(""" +## This test document shows Japanese output from the Reportlab PDF Library. +## You may use two fonts, HeiseiMin-W3 and HeiseiKakuGo-W5, and a number of +## different encodings. +## +## The available encoding names (with comments from the PDF specification) are: +## encodings_jpn = [ +## # official encoding names, comments taken verbatim from PDF Spec +## '83pv-RKSJ-H', #Macintosh, JIS X 0208 character set with KanjiTalk6 +## #extensions, Shift-JIS encoding, Script Manager code 1 +## '90ms-RKSJ-H', #Microsoft Code Page 932 (lfCharSet 0x80), JIS X 0208 +## #character set with NEC and IBM extensions +## '90ms-RKSJ-V', #Vertical version of 90ms-RKSJ-H +## '90msp-RKSJ-H', #Same as 90ms-RKSJ-H, but replaces half-width Latin +## #characters with proportional forms +## '90msp-RKSJ-V', #Vertical version of 90msp-RKSJ-H +## '90pv-RKSJ-H', #Macintosh, JIS X 0208 character set with KanjiTalk7 +## #extensions, Shift-JIS encoding, Script Manager code 1 +## 'Add-RKSJ-H', #JIS X 0208 character set with Fujitsu FMR extensions, +## #Shift-JIS encoding +## 'Add-RKSJ-V', #Vertical version of Add-RKSJ-H +## 'EUC-H', #JIS X 0208 character set, EUC-JP encoding +## 'EUC-V', #Vertical version of EUC-H +## 'Ext-RKSJ-H', #JIS C 6226 (JIS78) character set with NEC extensions, +## #Shift-JIS encoding +## 'Ext-RKSJ-V', #Vertical version of Ext-RKSJ-H +## 'H', #JIS X 0208 character set, ISO-2022-JP encoding, +## 'V', #Vertical version of H +## 'UniJIS-UCS2-H', #Unicode (UCS-2) encoding for the Adobe-Japan1 character +## #collection +## 'UniJIS-UCS2-V', #Vertical version of UniJIS-UCS2-H +## 'UniJIS-UCS2-HW-H', #Same as UniJIS-UCS2-H, but replaces proportional Latin +## #characters with half-width forms +## 'UniJIS-UCS2-HW-V' #Vertical version of UniJIS-UCS2-HW-H +## ] +## +## The next few pages show the complete character set available in the encoding +## "90ms-RKSJ-H" - Shift-JIS with the standard Microsoft extensions. +## """) +## c.drawText(tx) +## +## c.setFont('Helvetica',10) +## c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber()) +## +## +## +## c.showPage() + + from reportlab.lib import textsplit + + c.setFont('HeiseiMin-W3', 14) + y = 700 + c.drawString(70, y, 'cannot end line') + y -= 20 + for group in textsplit.CANNOT_START_LINE: + c.drawString(70, y, group) + y -= 20 + c.setFont('Helvetica',10) + c.drawString(70, y, ' '.join(map(lambda x: repr(x)[4:-1], group))) + c.setFont('HeiseiMin-W3', 14) + y -= 20 + + + + y -= 20 + c.drawString(70, y, 'cannot end line') + y -= 20 + for group in textsplit.CANNOT_END_LINE: + c.drawString(70, y, group) + y -= 20 + c.setFont('Helvetica',10) + c.drawString(70, y, ' '.join(map(lambda x: repr(x)[2:], group))) + c.setFont('HeiseiMin-W3', 14) + y -= 20 + + c.showPage() + + #utf8 encoded paragraph + sample2_uni = u'''\u30ac\u30c8\u30a6\u30a3\u30c3\u30af\u7a7a\u6e2f\u3068\u9023\u7d61\u901a + \u8def\u3067\u76f4\u7d50\u3055\u308c\u3066\u3044\u308b\u552f\u4e00\u306e\u30db\u30c6\u30eb + \u3067\u3042\u308b\u5f53\u30db\u30c6\u30eb\u306f\u3001\u8857\u306e\u4e2d\u5fc3\u90e8\u304b + \u308930\u5206\u306e\u5834\u6240\u306b\u3054\u3056\u3044\u307e\u3059\u3002\u5168\u5ba2\u5ba4 + \u306b\u9ad8\u901f\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u74b0\u5883\u3092\u5b8c\u5099 + \u3057\u3066\u304a\u308a\u307e\u3059\u3002\u30d5\u30a1\u30df\u30ea\u30fc\u30eb\u30fc\u30e0 + \u306f5\u540d\u69d8\u307e\u3067\u304a\u6cca\u308a\u3044\u305f\u3060\u3051\u307e\u3059\u3002 + \u307e\u305f\u3001\u30a8\u30b0\u30bc\u30af\u30c6\u30a3\u30d6\u30eb\u30fc\u30e0\u306e\u304a + \u5ba2\u69d8\u306f\u3001\u30a8\u30b0\u30bc\u30af\u30c6\u30a3\u30d6\u30e9\u30a6\u30f3\u30b8 + \u3092\u3054\u5229\u7528\u3044\u305f\u3060\u3051\u307e\u3059\u3002\u4e8b\u524d\u306b\u3054 + \u4e88\u7d04\u3044\u305f\u3060\u3051\u308b\u30bf\u30a4\u30e0\u30c8\u30a5\u30d5\u30e9\u30a4 + \u30fb\u30d1\u30c3\u30b1\u30fc\u30b8\u306b\u306f\u3001\u7a7a\u6e2f\u306e\u99d0\u8eca\u6599 + \u91d1\u304c\u542b\u307e\u308c\u3066\u304a\u308a\u307e\u3059\u3002''' + + oneline_uni = u''.join(sample2_uni.split()) + sample2_utf8 = oneline_uni.encode('utf8') + + from reportlab.platypus import Paragraph + from reportlab.lib.styles import ParagraphStyle + jsty = ParagraphStyle('japanese',fontName='HeiseiMin-W3', wordWrap='CJK') + jpara = Paragraph(oneline_uni, style=jsty) + + c.drawString(100, 710, 'Try to wrap a paragraph using a style with wordWrap="CJK"') + w, h = jpara.wrap(400,400) + jpara.drawOn(c, 100, 700 - h) + + #now try to split it... + c.drawString(100, 510, 'Now try to split a paragraph as if over a page break') + + topPara, bottomPara = jpara.split(400, 30) + w1, h1 = topPara.wrap(400, 30) + topPara.drawOn(c, 100, 450) + + w2, h2 = bottomPara.wrap(400, 30) + bottomPara.drawOn(c, 100, 400) + #print 'split into heights %0.2f, %0.2f' % (topPara.height, bottomPara.height) + + + + +## c.showPage() +## +## +## # full kuten chart in EUC +## c.setFont('Helvetica', 24) +## c.drawString(72,750, 'Characters available in JIS 0208-1997') +## y = 600 +## for row in range(1, 95): +## KutenRowCodeChart(row, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, y) +## y = y - 125 +## if y < 50: +## c.setFont('Helvetica',10) +## c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber()) +## c.showPage() +## y = 700 +## +## c.showPage() + + + #try with Unicode truetype - Mincho for starters +## import time +## started = time.clock() +## c.showPage() +## c.setFont('Helvetica',16) +## c.drawString(100,750, 'About to say Tokyo in MS Gothic...') +## +## from reportlab.pdfbase.ttfonts import TTFont, TTFontFile +## f = TTFontFile("msgothic.ttf") +## print f.name +## +## pdfmetrics.registerFont(TTFont(f.name, f)) +## +## utfText = u'Andr\202'.encode('utf8') +## c.setFont(f.name,16) +## c.drawString(100,700, utfText) +## +## +## #tokyoUCS2 = '\x67\x71\x4E\xAC' +## finished = time.clock() + + + + + + c.save() + + + if VERBOSE: + print 'saved test_multibyte_jpn.pdf' + + + def ___test2_all(self): + """Dumps out ALl GLYPHS in a CID font. + + Reach for your microscope :-)""" + try: + from reportlab.pdfbase.cidfonts import CIDFont, findCMapFile + findCMapFile('90ms-RKSJ-H') + findCMapFile('Identity-H') + except: + #don't have the font pack. return silently + return + + pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','Identity-H')) + + c = Canvas('test_japanese_2.pdf') + c.setFont('Helvetica', 30) + c.drawString(100,800, 'All Glyphs in Adobe-Japan-1-2 collection!') + + # the two typefaces + c.setFont('HeiseiMin-W3-Identity-H', 2) + + x0 = 50 + y0 = 700 + dx = 2 + dy = 2 + for row in range(256): + for cell in range(256): + s = chr(row) + chr(cell) + x = x0 + cell*dx + y = y0 - row*dy + c.drawString(x,y,s) + + c.save() + if VERBOSE: + print 'saved '+outputfile('test_multibyte_jpn.pdf') + + +def makeSuite(): + return makeSuiteForClasses(JapaneseFontTests) + + +#noruntests +if __name__ == "__main__": + VERBOSE = 1 + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_multibyte_kor.py b/bin/reportlab/test/test_multibyte_kor.py new file mode 100644 index 00000000000..8f100b6627b --- /dev/null +++ b/bin/reportlab/test/test_multibyte_kor.py @@ -0,0 +1,125 @@ + +import string, os + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfgen.canvas import Canvas +from reportlab.lib import colors +from reportlab.lib.codecharts import KutenRowCodeChart, hBoxText +from reportlab.pdfbase.cidfonts import UnicodeCIDFont, findCMapFile +global VERBOSE +VERBOSE = 0 + + + +class KoreanFontTests(unittest.TestCase): + + def test0(self): + + # if they do not have the font files or encoding, go away quietly +## try: +## from reportlab.pdfbase.cidfonts import CIDFont, findCMapFile +## findCMapFile('KSCms-UHC-H') +## except: +## #don't have the font pack. return silently +## print 'CMap not found' +## return + + localFontName = 'HYSMyeongJo-Medium' + c = Canvas(outputfile('test_multibyte_kor.pdf')) + c.setFont('Helvetica', 30) + c.drawString(100,700, 'Korean Font Support') + c.setFont('Helvetica', 10) + c.drawString(100,680, 'Short sample in Unicode; grey area should outline the text with correct width.') + + + hBoxText(u'\ub300\ud55c\ubbfc\uad6d = Korea', + c, 100, 660, 'HYSMyeongJo-Medium') + hBoxText(u'\uc548\uc131\uae30 = AHN Sung-Gi (Actor)', + c, 100, 640, 'HYGothic-Medium') + +## pdfmetrics.registerFont(UnicodeCIDFont('HYSMyeongJo-Medium')) +## c.setFont('Helvetica', 10) +## c.drawString(100,610, "Longer sample From Adobe's Acrobat web page in EUC:") +## +## sample = """\xbf\xad \xbc\xf6 \xbe\xf8\xb4\xc2 \xb9\xae\xbc\xad\xb4\xc2 \xbe\xc6\xb9\xab\xb7\xb1 \xbc\xd2\xbf\xeb\xc0\xcc \xbe\xf8\xbd\xc0\xb4\xcf\xb4\xd9. \xbb\xe7\xbe\xf7 \xb0\xe8\xc8\xb9\xbc\xad, \xbd\xba\xc7\xc1\xb7\xb9\xb5\xe5\xbd\xc3\xc6\xae, \xb1\xd7\xb7\xa1\xc7\xc8\xc0\xcc \xb8\xb9\xc0\xcc \xc6\xf7\xc7\xd4\xb5\xc8 \xbc\xd2\xc3\xa5\xc0\xda \xb6\xc7\xb4\xc2 \xc0\xa5 +##\xbb\xe7\xc0\xcc\xc6\xae\xb8\xa6 \xc0\xdb\xbc\xba\xc7\xcf\xb4\xc2 \xb0\xe6\xbf\xec Adobe\xa2\xe7 Acrobat\xa2\xe7 5.0 \xbc\xd2\xc7\xc1\xc6\xae\xbf\xfe\xbe\xee\xb8\xa6 \xbb\xe7\xbf\xeb\xc7\xd8\xbc\xad \xc7\xd8\xb4\xe7 \xb9\xae\xbc\xad\xb8\xa6 Adobe +##Portable Document Format (PDF) \xc6\xc4\xc0\xcf\xb7\xce \xba\xaf\xc8\xaf\xc7\xd2 \xbc\xf6 \xc0\xd6\xbd\xc0\xb4\xcf\xb4\xd9. \xb4\xa9\xb1\xb8\xb3\xaa \xb1\xa4\xb9\xfc\xc0\xa7\xc7\xd1 \xc1\xbe\xb7\xf9\xc0\xc7 +##\xc7\xcf\xb5\xe5\xbf\xfe\xbe\xee\xbf\xcd \xbc\xd2\xc7\xc1\xc6\xae\xbf\xfe\xbe\xee\xbf\xa1\xbc\xad \xb9\xae\xbc\xad\xb8\xa6 \xbf\xad \xbc\xf6 \xc0\xd6\xc0\xb8\xb8\xe7 \xb7\xb9\xc0\xcc\xbe\xc6\xbf\xf4, \xc6\xf9\xc6\xae, \xb8\xb5\xc5\xa9, \xc0\xcc\xb9\xcc\xc1\xf6 \xb5\xee\xc0\xbb \xbf\xf8\xba\xbb \xb1\xd7\xb4\xeb\xb7\xce \xc0\xc7\xb5\xb5\xc7\xd1 \xb9\xd9 \xb4\xeb\xb7\xce +##\xc7\xa5\xbd\xc3\xc7\xd2 \xbc\xf6 \xc0\xd6\xbd\xc0\xb4\xcf\xb4\xd9. Acrobat 5.0\xc0\xbb \xbb\xe7\xbf\xeb\xc7\xcf\xbf\xa9 \xc0\xa5 \xba\xea\xb6\xf3\xbf\xec\xc0\xfa\xbf\xa1\xbc\xad \xb9\xae\xbc\xad\xb8\xa6 \xbd\xc2\xc0\xce\xc7\xcf\xb0\xed \xc1\xd6\xbc\xae\xc0\xbb \xc3\xdf\xb0\xa1\xc7\xcf\xb4\xc2 \xb9\xe6\xbd\xc4\xc0\xb8\xb7\xce +##\xb1\xe2\xbe\xf7\xc0\xc7 \xbb\xfd\xbb\xea\xbc\xba\xc0\xbb \xc7\xe2\xbb\xf3\xbd\xc3\xc5\xb3 \xbc\xf6 \xc0\xd6\xbd\xc0\xb4\xcf\xb4\xd9. +## +##\xc0\xfa\xc0\xdb\xb1\xc7 © 2001 Adobe Systems Incorporated. \xb8\xf0\xb5\xe7 \xb1\xc7\xb8\xae\xb0\xa1 \xba\xb8\xc8\xa3\xb5\xcb\xb4\xcf\xb4\xd9. +##\xbb\xe7\xbf\xeb\xc0\xda \xbe\xe0\xb0\xfc +##\xbf\xc2\xb6\xf3\xc0\xce \xbb\xe7\xbf\xeb\xc0\xda \xba\xb8\xc8\xa3 \xb1\xd4\xc1\xa4 +##Adobe\xc0\xc7 \xc0\xe5\xbe\xd6\xc0\xda \xc1\xf6\xbf\xf8 +##\xbc\xd2\xc7\xc1\xc6\xae\xbf\xfe\xbe\xee \xba\xd2\xb9\xfd \xc0\xcc\xbf\xeb \xb9\xe6\xc1\xf6 +##""" +## tx = c.beginText(100,600) +## tx.setFont('HYSMyeongJo-Medium-KSC-EUC-H', 7, 8) +## tx.textLines(sample) +## tx.setFont('Helvetica', 10, 12) +## tx.textLine() +## tx.textLines("""This test document shows Korean output from the Reportlab PDF Library. +## You may use one Korean font, HYSMyeongJo-Medium, and a number of different +## encodings. +## +## The available encoding names (with comments from the PDF specification) are: +## encodings_kor = [ +## 'KSC-EUC-H', # KS X 1001:1992 character set, EUC-KR encoding +## 'KSC-EUC-V', # Vertical version of KSC-EUC-H +## 'KSCms-UHC-H', # Microsoft Code Page 949 (lfCharSet 0x81), KS X 1001:1992 +## #character set plus 8,822 additional hangul, Unified Hangul +## #Code (UHC) encoding +## 'KSCms-UHC-V', #Vertical version of KSCms-UHC-H +## 'KSCms-UHC-HW-H', #Same as KSCms-UHC-H, but replaces proportional Latin +## # characters with halfwidth forms +## 'KSCms-UHC-HW-V', #Vertical version of KSCms-UHC-HW-H +## 'KSCpc-EUC-H', #Macintosh, KS X 1001:1992 character set with MacOS-KH +## #extensions, Script Manager Code 3 +## 'UniKS-UCS2-H', #Unicode (UCS-2) encoding for the Adobe-Korea1 character collection +## 'UniKS-UCS2-V' #Vertical version of UniKS-UCS2-H +## ] +## +## The following pages show all characters in the KS X 1001:1992 standard, using the +## encoding 'KSC-EUC-H' above. More characters (a LOT more) are available if you +## use UHC encoding or the Korean Unicode subset, for which the correct encoding +## names are also listed above. +## """) +## +## c.drawText(tx) +## +## c.setFont('Helvetica',10) +## c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber()) +## c.showPage() +## +## # full kuten chart in EUC +## c.setFont('Helvetica', 18) +## c.drawString(72,750, 'Characters available in KS X 1001:1992, EUC encoding') +## y = 600 +## for row in range(1, 95): +## KutenRowCodeChart(row, 'HYSMyeongJo-Medium','KSC-EUC-H').drawOn(c, 72, y) +## y = y - 125 +## if y < 50: +## c.setFont('Helvetica',10) +## c.drawCentredString(297, 36, 'Page %d' % c.getPageNumber()) +## c.showPage() +## y = 700 + + c.save() + + if VERBOSE: + print 'saved '+outputfile('test_multibyte_kor.pdf') + + +def makeSuite(): + return makeSuiteForClasses(KoreanFontTests) + + +#noruntests +if __name__ == "__main__": + VERBOSE = 1 + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_paragraphs.py b/bin/reportlab/test/test_paragraphs.py new file mode 100644 index 00000000000..bd11843e6db --- /dev/null +++ b/bin/reportlab/test/test_paragraphs.py @@ -0,0 +1,174 @@ +#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/test/test_paragraphs.py +# tests some paragraph styles + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.platypus import Paragraph, SimpleDocTemplate, XBox, Indenter, XPreformatted +from reportlab.lib.styles import ParagraphStyle +from reportlab.lib.units import inch +from reportlab.lib.colors import red, black, navy, white, green +from reportlab.lib.randomtext import randomText +from reportlab.rl_config import defaultPageSize + +(PAGE_WIDTH, PAGE_HEIGHT) = defaultPageSize + + +def myFirstPage(canvas, doc): + canvas.saveState() + canvas.setStrokeColor(red) + canvas.setLineWidth(5) + canvas.line(66,72,66,PAGE_HEIGHT-72) + canvas.setFont('Times-Bold',24) + canvas.drawString(108, PAGE_HEIGHT-54, "TESTING PARAGRAPH STYLES") + canvas.setFont('Times-Roman',12) + canvas.drawString(4 * inch, 0.75 * inch, "First Page") + canvas.restoreState() + + +def myLaterPages(canvas, doc): + canvas.saveState() + canvas.setStrokeColor(red) + canvas.setLineWidth(5) + canvas.line(66,72,66,PAGE_HEIGHT-72) + canvas.setFont('Times-Roman',12) + canvas.drawString(4 * inch, 0.75 * inch, "Page %d" % doc.page) + canvas.restoreState() + + +class ParagraphTestCase(unittest.TestCase): + "Test Paragraph class (eyeball-test)." + + def test0(self): + """Test... + + The story should contain... + + Features to be visually confirmed by a human being are: + + 1. ... + 2. ... + 3. ... + """ + + story = [] + + #need a style + styNormal = ParagraphStyle('normal') + styGreen = ParagraphStyle('green',parent=styNormal,textColor=green) + + # some to test + stySpaced = ParagraphStyle('spaced', + parent=styNormal, + spaceBefore=12, + spaceAfter=12) + + + story.append( + Paragraph("This is a normal paragraph. " + + randomText(), styNormal)) + story.append( + Paragraph("This has 12 points space before and after, set in the style. " + + randomText(), stySpaced)) + story.append( + Paragraph("This is normal. " + + randomText(), styNormal)) + + story.append( + Paragraph(""" + This has 12 points space before and after, set inline with + XML tag. It works too.""" + randomText() + "This got a background from the para tag""", styNormal)) + + + story.append( + Paragraph("""\n\tThis has newlines and tabs on the front but inside the para tag""", styNormal)) + story.append( + Paragraph(""" This has spaces on the front but inside the para tag""", styNormal)) + + story.append( + Paragraph("""\n\tThis has newlines and tabs on the front but no para tag""", styNormal)) + story.append( + Paragraph(""" This has spaces on the front but no para tag""", styNormal)) + + story.append(Paragraph("""This has blue text here.""", styNormal)) + story.append(Paragraph("""This has italic text here.""", styNormal)) + story.append(Paragraph("""This has bold text here.""", styNormal)) + story.append(Paragraph("""This has underlined text here.""", styNormal)) + story.append(Paragraph("""This has blue and red underlined text here.""", styNormal)) + story.append(Paragraph("""green underlining""", styGreen)) + story.append(Paragraph("""green underlining""", styGreen)) + story.append(Paragraph("""This has m2 a superscript.""", styNormal)) + story.append(Paragraph("""This has m2 a subscript. Like H2O!""", styNormal)) + story.append(Paragraph("""This has a font change to Helvetica.""", styNormal)) + #This one fails: + #story.append(Paragraph("""This has a font change to Helvetica-Oblique.""", styNormal)) + story.append(Paragraph("""This has a font change to Helvetica in italics.""", styNormal)) + + story.append(Paragraph('''This one uses upper case tags and has set caseSensitive=0: Here comes Helvetica 14 with strong emphasis.''', styNormal, caseSensitive=0)) + story.append(Paragraph('''The same as before, but has set not set caseSensitive, thus the tags are ignored: Here comes Helvetica 14 with strong emphasis.''', styNormal)) + story.append(Paragraph('''This one uses fonts with size "14pt" and also uses the em and strong tags: Here comes Helvetica 14 with strong emphasis.''', styNormal, caseSensitive=0)) + story.append(Paragraph('''This uses a font size of 3cm: Here comes Courier 3cm and normal again.''', styNormal, caseSensitive=0)) + story.append(Paragraph('''This is just a very long silly text to see if the caseSensitive flag also works if the paragraph is very long. '''*20, styNormal, caseSensitive=0)) + story.append(Indenter("1cm")) + story.append(Paragraph(")Indented list. %s" % randomText(), styNormal)) + story.append(Paragraph(")Indented list. %s" % randomText(), styNormal)) + story.append(Paragraph(")Indented list. %s" % randomText(), styNormal)) + story.append(Indenter("1cm")) + story.append(XPreformatted(")Indented list.", styNormal)) + story.append(XPreformatted(")Indented list.", styNormal)) + story.append(Indenter("-1cm")) + story.append(Paragraph(")Indented list. %s" % randomText(), styNormal)) + story.append(Indenter("-1cm")) + story.append(Paragraph("Indented list using seqChain/Format", stySpaced)) + story.append(Indenter("1cm")) + story.append(Paragraph(")Indented list. %s" % randomText(), styNormal)) + story.append(Paragraph(")Indented list. %s" % randomText(), styNormal)) + story.append(Paragraph(")Indented list. %s" % randomText(), styNormal)) + story.append(Indenter("1cm")) + story.append(XPreformatted(")Indented list.", styNormal)) + story.append(XPreformatted(")Indented list.", styNormal)) + story.append(Indenter("-1cm")) + story.append(Paragraph(")Indented list. %s" % randomText(), styNormal)) + story.append(Indenter("1cm")) + story.append(XPreformatted(")Indented list.", styNormal)) + story.append(Indenter("1cm")) + story.append(XPreformatted(")Indented list. line1", styNormal)) + story.append(XPreformatted(")Indented list. line2", styNormal)) + story.append(Indenter("-1cm")) + story.append(XPreformatted(")Indented list.", styNormal)) + story.append(Indenter("-1cm")) + story.append(Indenter("-1cm")) + + template = SimpleDocTemplate(outputfile('test_paragraphs.pdf'), + showBoundary=1) + template.build(story, + onFirstPage=myFirstPage, onLaterPages=myLaterPages) + + +def makeSuite(): + return makeSuiteForClasses(ParagraphTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_pdfbase_encodings.py b/bin/reportlab/test/test_pdfbase_encodings.py new file mode 100644 index 00000000000..2c92a984a8c --- /dev/null +++ b/bin/reportlab/test/test_pdfbase_encodings.py @@ -0,0 +1,255 @@ +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfgen.canvas import Canvas +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont +from reportlab.pdfbase import pdfutils + +from reportlab.platypus.paragraph import Paragraph +from reportlab.lib.styles import ParagraphStyle +from reportlab.graphics.shapes import Drawing, String, Ellipse +import re +import codecs +textPat = re.compile(r'\([^(]*\)') + +#test sentences +testCp1252 = 'copyright %s trademark %s registered %s ReportLab! Ol%s!' % (chr(169), chr(153),chr(174), chr(0xe9)) +testUni = unicode(testCp1252, 'cp1252') +testUTF8 = testUni.encode('utf-8') +# expected result is octal-escaped text in the PDF +expectedCp1252 = pdfutils._escape(testCp1252) + +def extractText(pdfOps): + """Utility to rip out the PDF text within a block of PDF operators. + + PDF will show a string draw as something like "(Hello World) Tj" + i.e. text is in curved brackets. Crude and dirty, probably fails + on escaped brackets. + """ + found = textPat.findall(pdfOps) + #chop off '(' and ')' + return map(lambda x:x[1:-1], found) + +def subsetToUnicode(ttf, subsetCodeStr): + """Return unicode string represented by given subsetCode string + as found when TrueType font rendered to PDF, ttf must be the font + object that was used.""" + # This relies on TTFont internals and uses the first document + # and subset it finds + subset = ttf.state.values()[0].subsets[0] + chrs = [] + for codeStr in subsetCodeStr.split('\\'): + if codeStr: + chrs.append(unichr(subset[int(codeStr[1:], 8)])) + return u''.join(chrs) + +class TextEncodingTestCase(unittest.TestCase): + """Tests of expected Unicode and encoding behaviour + """ + def setUp(self): + self.luxi = TTFont("Luxi", "luxiserif.ttf") + pdfmetrics.registerFont(self.luxi) + self.styNormal = ParagraphStyle(name='Helvetica', fontName='Helvetica-Oblique') + self.styTrueType = ParagraphStyle(name='TrueType', fontName='luxi') + + def testStringWidth(self): + msg = 'Hello World' + assert abs(pdfmetrics.stringWidth(msg, 'Courier', 10) - 66.0) < 0.01 + assert abs(pdfmetrics.stringWidth(msg, 'Helvetica', 10) - 51.67) < 0.01 + assert abs(pdfmetrics.stringWidth(msg, 'Times-Roman', 10) - 50.27) < 0.01 + assert abs(pdfmetrics.stringWidth(msg, 'Luxi', 10) - 50.22) < 0.01 + + uniMsg1 = u"Hello World" + assert abs(pdfmetrics.stringWidth(uniMsg1, 'Courier', 10) - 66.0) < 0.01 + assert abs(pdfmetrics.stringWidth(uniMsg1, 'Helvetica', 10) - 51.67) < 0.01 + assert abs(pdfmetrics.stringWidth(uniMsg1, 'Times-Roman', 10) - 50.27) < 0.01 + assert abs(pdfmetrics.stringWidth(uniMsg1, 'Luxi', 10) - 50.22) < 0.01 + + + # Courier are all 600 ems wide. So if one 'measures as utf8' one will + # get a wrong width as extra characters are seen + assert len(testCp1252) == 52 + assert abs(pdfmetrics.stringWidth(testCp1252, 'Courier', 10, 'cp1252') - 312.0) < 0.01 + # the test string has 5 more bytes and so "measures too long" if passed to + # a single-byte font which treats it as a single-byte string. + assert len(testUTF8)==57 + assert abs(pdfmetrics.stringWidth(testUTF8, 'Courier', 10) - 312.0) < 0.01 + + assert len(testUni)==52 + assert abs(pdfmetrics.stringWidth(testUni, 'Courier', 10) - 312.0) < 0.01 + + + # now try a TrueType font. Should be able to accept Unicode or UTF8 + assert abs(pdfmetrics.stringWidth(testUTF8, 'Luxi', 10) - 224.44) < 0.01 + assert abs(pdfmetrics.stringWidth(testUni, 'Luxi', 10) - 224.44) < 0.01 + + def testUtf8Canvas(self): + """Verify canvas declared as utf8 autoconverts. + + This assumes utf8 input. It converts to the encoding of the + underlying font, so both text lines APPEAR the same.""" + + + c = Canvas(outputfile('test_pdfbase_encodings_utf8.pdf')) + + c.drawString(100,700, testUTF8) + + # Set a font with UTF8 encoding + c.setFont('Luxi', 12) + + # This should pass the UTF8 through unchanged + c.drawString(100,600, testUTF8) + # and this should convert from Unicode to UTF8 + c.drawString(100,500, testUni) + + + # now add a paragraph in Latin-1 in the latin-1 style + p = Paragraph(testUTF8, style=self.styNormal, encoding="utf-8") + w, h = p.wrap(150, 100) + p.drawOn(c, 100, 400) #3 + c.rect(100,300,w,h) + + # now add a paragraph in UTF-8 in the UTF-8 style + p2 = Paragraph(testUTF8, style=self.styTrueType, encoding="utf-8") + w, h = p2.wrap(150, 100) + p2.drawOn(c, 300, 400) #4 + c.rect(100,300,w,h) + + # now add a paragraph in Unicode in the latin-1 style + p3 = Paragraph(testUni, style=self.styNormal) + w, h = p3.wrap(150, 100) + p3.drawOn(c, 100, 300) + c.rect(100,300,w,h) + + # now add a paragraph in Unicode in the UTF-8 style + p4 = Paragraph(testUni, style=self.styTrueType) + p4.wrap(150, 100) + p4.drawOn(c, 300, 300) + c.rect(300,300,w,h) + + # now a graphic + d1 = Drawing(400,50) + d1.add(Ellipse(200,25,200,12.5, fillColor=None)) + d1.add(String(200,25,testUTF8, textAnchor='middle', encoding='utf-8')) + d1.drawOn(c, 100, 150) + + # now a graphic in utf8 + d2 = Drawing(400,50) + d2.add(Ellipse(200,25,200,12.5, fillColor=None)) + d2.add(String(200,25,testUTF8, fontName='Luxi', textAnchor='middle', encoding='utf-8')) + d2.drawOn(c, 100, 100) + + # now a graphic in Unicode with T1 font + d3 = Drawing(400,50) + d3.add(Ellipse(200,25,200,12.5, fillColor=None)) + d3.add(String(200,25,testUni, textAnchor='middle')) + d3.drawOn(c, 100, 50) + + # now a graphic in Unicode with TT font + d4 = Drawing(400,50) + d4.add(Ellipse(200,25,200,12.5, fillColor=None)) + d4.add(String(200,25,testUni, fontName='Luxi', textAnchor='middle')) + d4.drawOn(c, 100, 0) + + extracted = extractText(c.getCurrentPageContent()) + self.assertEquals(extracted[0], expectedCp1252) + self.assertEquals(extracted[1], extracted[2]) + #self.assertEquals(subsetToUnicode(self.luxi, extracted[1]), testUni) + c.save() + +class FontEncodingTestCase(unittest.TestCase): + """Make documents with custom encodings of Type 1 built-in fonts. + + Nothing really to do with character encodings; this is about hacking the font itself""" + + def test0(self): + "Make custom encodings of standard fonts" + + # make a custom encoded font. + c = Canvas(outputfile('test_pdfbase_encodings.pdf')) + c.setPageCompression(0) + c.setFont('Helvetica', 12) + c.drawString(100, 700, 'The text below should be in a custom encoding in which all vowels become "z"') + + # invent a new language where vowels are replaced with letter 'z' + zenc = pdfmetrics.Encoding('EncodingWithoutVowels', 'WinAnsiEncoding') + for ch in 'aeiou': + zenc[ord(ch)] = 'z' + for ch in 'AEIOU': + zenc[ord(ch)] = 'Z' + pdfmetrics.registerEncoding(zenc) + + # now we can make a font based on this encoding + # AR hack/workaround: the name of the encoding must be a Python codec! + f = pdfmetrics.Font('FontWithoutVowels', 'Helvetica-Oblique', 'EncodingWithoutVowels') + pdfmetrics.registerFont(f) + + c.setFont('FontWithoutVowels', 12) + c.drawString(125, 675, "The magic word is squamish ossifrage") + + # now demonstrate adding a Euro to MacRoman, which lacks one + c.setFont('Helvetica', 12) + c.drawString(100, 650, "MacRoman encoding lacks a Euro. We'll make a Mac font with the Euro at #219:") + + # WinAnsi Helvetica + pdfmetrics.registerFont(pdfmetrics.Font('Helvetica-WinAnsi', 'Helvetica-Oblique', 'WinAnsiEncoding')) + c.setFont('Helvetica-WinAnsi', 12) + c.drawString(125, 625, 'WinAnsi with Euro: character 128 = "\200"') + + pdfmetrics.registerFont(pdfmetrics.Font('MacHelvNoEuro', 'Helvetica-Oblique', 'MacRomanEncoding')) + c.setFont('MacHelvNoEuro', 12) + c.drawString(125, 600, 'Standard MacRoman, no Euro: Character 219 = "\333"') # oct(219)=0333 + + # now make our hacked encoding + euroMac = pdfmetrics.Encoding('MacWithEuro', 'MacRomanEncoding') + euroMac[219] = 'Euro' + pdfmetrics.registerEncoding(euroMac) + + pdfmetrics.registerFont(pdfmetrics.Font('MacHelvWithEuro', 'Helvetica-Oblique', 'MacWithEuro')) + + c.setFont('MacHelvWithEuro', 12) + c.drawString(125, 575, 'Hacked MacRoman with Euro: Character 219 = "\333"') # oct(219)=0333 + + # now test width setting with and without _rl_accel - harder + # make an encoding where 'm' becomes 'i' + c.setFont('Helvetica', 12) + c.drawString(100, 500, "Recode 'm' to 'i' and check we can measure widths. Boxes should surround letters.") + sample = 'Mmmmm. ' * 6 + 'Mmmm' + + c.setFont('Helvetica-Oblique',12) + c.drawString(125, 475, sample) + w = c.stringWidth(sample, 'Helvetica-Oblique', 12) + c.rect(125, 475, w, 12) + + narrowEnc = pdfmetrics.Encoding('m-to-i') + narrowEnc[ord('m')] = 'i' + narrowEnc[ord('M')] = 'I' + pdfmetrics.registerEncoding(narrowEnc) + + pdfmetrics.registerFont(pdfmetrics.Font('narrow', 'Helvetica-Oblique', 'm-to-i')) + c.setFont('narrow', 12) + c.drawString(125, 450, sample) + w = c.stringWidth(sample, 'narrow', 12) + c.rect(125, 450, w, 12) + + c.setFont('Helvetica', 12) + c.drawString(100, 400, "Symbol & Dingbats fonts - check we still get valid PDF in StandardEncoding") + c.setFont('Symbol', 12) + c.drawString(100, 375, 'abcdefghijklmn') + c.setFont('ZapfDingbats', 12) + c.drawString(300, 375, 'abcdefghijklmn') + + c.save() + +def makeSuite(): + return makeSuiteForClasses( + TextEncodingTestCase, + + #FontEncodingTestCase - nobbled for now due to old stuff which needs removing. + ) + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_pdfbase_fontembed.py b/bin/reportlab/test/test_pdfbase_fontembed.py new file mode 100644 index 00000000000..e8b37165e1f --- /dev/null +++ b/bin/reportlab/test/test_pdfbase_fontembed.py @@ -0,0 +1,90 @@ +import os + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfgen.canvas import Canvas +from reportlab.pdfbase import pdfmetrics +from reportlab.test.test_pdfbase_pdfmetrics import makeWidthTestForAllGlyphs + + +class EmbeddingTestCase(unittest.TestCase): + "Make documents with embedded fonts" + + def test0(self): + """Make documents with embedded fonts. + + Just vam Rossum has kindly donated a font which we may use + for testing purposes. You need to contact him at just@letterror.com + if you want to use it for real.""" + + #LettError fonts should always be there. The others are voluntary. + + ok = 1 + + c = Canvas(outputfile('test_pdfbase_fontembed.pdf')) + c.setPageCompression(0) + c.setFont('Helvetica', 12) + c.drawString(100, 700, 'This is Helvetica. The text below should be different fonts...') + + if os.path.isfile('GDB_____.AFM') and os.path.isfile('GDB_____.PFB'): + # a normal text font + garaFace = pdfmetrics.EmbeddedType1Face('GDB_____.AFM','GDB_____.PFB') + faceName = 'AGaramond-Bold' # pulled from AFM file + pdfmetrics.registerTypeFace(garaFace) + + garaFont = pdfmetrics.Font('MyGaramondBold', faceName, 'WinAnsiEncoding') + pdfmetrics.registerFont(garaFont) + + c.setFont('AGaramond-Bold', 12) + c.drawString(100, 650, 'This should be in AGaramond-Bold') + + if os.path.isfile('CR______.AFM') and os.path.isfile('CR______.PFB'): + + # one with a custom encoding + cartaFace = pdfmetrics.EmbeddedType1Face('CR______.AFM','CR______.PFB') + faceName = 'Carta' # pulled from AFM file + pdfmetrics.registerTypeFace(cartaFace) + + cartaFont = pdfmetrics.Font('Carta', 'Carta', 'CartaEncoding') + pdfmetrics.registerFont(cartaFont) + + text = 'This should be in Carta, a map symbol font:' + c.setFont('Helvetica', 12) + c.drawString(100, 600, text) + w = c.stringWidth(text, 'Helvetica', 12) + + c.setFont('Carta', 12) + c.drawString(100+w, 600, ' Hello World') + + # LettError sample - creates on demand, we hope + y = 550 +## justFace = pdfmetrics.EmbeddedType1Face('LeERC___.AFM','LeERC___.PFB') +## +## faceName = 'LettErrorRobot-Chrome' # pulled from AFM file +## pdfmetrics.registerTypeFace(justFace) +## +## justFont = pdfmetrics.Font('LettErrorRobot-Chrome', faceName, 'WinAnsiEncoding') +## pdfmetrics.registerFont(justFont) + + c.setFont('LettErrorRobot-Chrome', 12) + c.drawString(100, y, 'This should be in LettErrorRobot-Chrome') + + def testNamedFont(canv, fontName): + canv.showPage() + makeWidthTestForAllGlyphs(canv, fontName, outlining=0) + + testNamedFont(c, 'LettErrorRobot-Chrome') + + c.save() + + + +def makeSuite(): + return makeSuiteForClasses(EmbeddingTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_pdfbase_pdfmetrics.py b/bin/reportlab/test/test_pdfbase_pdfmetrics.py new file mode 100644 index 00000000000..abfb9c06261 --- /dev/null +++ b/bin/reportlab/test/test_pdfbase_pdfmetrics.py @@ -0,0 +1,121 @@ +#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/test/test_pdfbase_pdfmetrics.py +#test_pdfbase_pdfmetrics_widths +""" +Various tests for PDF metrics. + +The main test prints out a PDF documents enabling checking of widths of every +glyph in every standard font. Long! +""" +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase import _fontdata +from reportlab.pdfgen.canvas import Canvas +from reportlab.lib import colors + +verbose = 0 +fontNamesToTest = _fontdata.standardFonts #[0:12] #leaves out Symbol and Dingbats for now + + +def decoratePage(c, header): + c.setFont('Helvetica-Oblique',10) + c.drawString(72, 800, header) + c.drawCentredString(297, 54, 'Page %d' % c.getPageNumber()) + + +def makeWidthTestForAllGlyphs(canv, fontName, outlining=1): + """New page, then runs down doing all the glyphs in one encoding""" + thisFont = pdfmetrics.getFont(fontName) + encName = thisFont.encName + canv.setFont('Helvetica-Bold', 12) + title = 'Glyph Metrics Test for font %s, ascent=%s, descent=%s, encoding=%s' % (fontName, str(thisFont.face.ascent), str(thisFont.face.descent), encName) + canv.drawString(80, 750, title) + canv.setFont('Helvetica-Oblique',10) + canv.drawCentredString(297, 54, 'Page %d' % canv.getPageNumber()) + + if outlining: + # put it in the outline + canv.bookmarkPage('GlyphWidths:' + fontName) + canv.addOutlineEntry(fontName,'GlyphWidths:' + fontName, level=1) + + y = 720 + widths = thisFont.widths + glyphNames = thisFont.encoding.vector + # need to get the right list of names for the font in question + for i in range(256): + if y < 72: + canv.showPage() + decoratePage(canv, title) + y = 750 + glyphName = glyphNames[i] + if glyphName is not None: + canv.setFont('Helvetica', 10) + text = unicode(chr(i),encName).encode('utf8')*30 + try: + w = canv.stringWidth(text, fontName, 10) + canv.drawString(80, y, '%03d %s w=%3d' % (i, glyphName, int((w/3.)*10))) + canv.setFont(fontName, 10) + canv.drawString(200, y, text) + + # now work out width and put a red marker next to the end. + canv.setFillColor(colors.red) + canv.rect(200 + w, y-1, 5, 10, stroke=0, fill=1) + canv.setFillColor(colors.black) + except KeyError: + canv.drawString(200, y, 'Could not find glyph named "%s"' % glyphName) + y = y - 12 + + +def makeTestDoc(fontNames): + filename = outputfile('test_pdfbase_pdfmetrics.pdf') + c = Canvas(filename) + c.bookmarkPage('Glyph Width Tests') + c.showOutline() + c.addOutlineEntry('Glyph Width Tests', 'Glyph Width Tests', level=0) + if verbose: + print # get it on a different line to the unittest log output. + for fontName in fontNames: + if verbose: + print 'width test for', fontName + + makeWidthTestForAllGlyphs(c, fontName) + c.showPage() + c.save() + if verbose: + if verbose: + print 'saved',filename + + +class PDFMetricsTestCase(unittest.TestCase): + "Test various encodings used in PDF files." + + def test0(self): + "Visual test for correct glyph widths" + makeTestDoc(fontNamesToTest) + + +def makeSuite(): + return makeSuiteForClasses(PDFMetricsTestCase) + + +#noruntests +if __name__=='__main__': + usage = """Usage: + (1) test_pdfbase_pdfmetrics.py - makes doc for all standard fonts + (2) test_pdfbase_pdfmetrics.py fontname - " " for just one font.""" + import sys + verbose = 1 + # accept font names as arguments; otherwise it does the lot + if len(sys.argv) > 1: + for arg in sys.argv[1:]: + if not arg in fontNamesToTest: + print 'unknown font %s' % arg + print usage + sys.exit(0) + + fontNamesToTest = sys.argv[1:] + + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_pdfbase_pdfutils.py b/bin/reportlab/test/test_pdfbase_pdfutils.py new file mode 100644 index 00000000000..8c042e6977e --- /dev/null +++ b/bin/reportlab/test/test_pdfbase_pdfutils.py @@ -0,0 +1,52 @@ +#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/test/test_pdfbase_pdfutils.py +"""Tests for utility functions in reportlab.pdfbase.pdfutils. +""" + + +import os + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, printLocation + +from reportlab.pdfbase.pdfutils import _AsciiHexEncode, _AsciiHexDecode +from reportlab.pdfbase.pdfutils import _AsciiBase85Encode, _AsciiBase85Decode + + +class PdfEncodingTestCase(unittest.TestCase): + "Test various encodings used in PDF files." + + def testAsciiHex(self): + "Test if the obvious test for whether ASCII-Hex encoding works." + + plainText = 'What is the average velocity of a sparrow?' + encoded = _AsciiHexEncode(plainText) + decoded = _AsciiHexDecode(encoded) + + msg = "Round-trip AsciiHex encoding failed." + assert decoded == plainText, msg + + + def testAsciiBase85(self): + "Test if the obvious test for whether ASCII-Base85 encoding works." + + msg = "Round-trip AsciiBase85 encoding failed." + plain = 'What is the average velocity of a sparrow?' + + #the remainder block can be absent or from 1 to 4 bytes + for i in xrange(55): + encoded = _AsciiBase85Encode(plain) + decoded = _AsciiBase85Decode(encoded) + assert decoded == plain, msg + plain = plain + chr(i) + + +def makeSuite(): + return makeSuiteForClasses(PdfEncodingTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_pdfbase_postscript.py b/bin/reportlab/test/test_pdfbase_postscript.py new file mode 100644 index 00000000000..e5642502406 --- /dev/null +++ b/bin/reportlab/test/test_pdfbase_postscript.py @@ -0,0 +1,77 @@ +#!/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/test/test_pdfbase_postscript.py +__version__=''' $Id''' +__doc__="""Tests Postscript XObjects. + +Nothing visiblke in Acrobat, but the resulting files +contain graphics and tray commands if exported to +a Postscript device in Acrobat 4.0""" + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation +from reportlab.pdfgen.canvas import Canvas + + +class PostScriptTestCase(unittest.TestCase): + "Simplest test that makes PDF" + + def testVisible(self): + "Makes a document with extra text - should export and distill" + c = Canvas(outputfile('test_pdfbase_postscript_visible.pdf')) + c.setPageCompression(0) + + c.setFont('Helvetica-Bold', 18) + c.drawString(100,700, 'Hello World. This is page 1 of a 2 page document.') + c.showPage() + + c.setFont('Helvetica-Bold', 16) + c.drawString(100,700, 'Page 2. This has some postscript drawing code.') + c.drawString(100,680, 'If you print it using a PS device and Acrobat 4/5,') + c.drawString(100,660, 'or export to Postscript, you should see the word') + c.drawString(100,640, '"Hello PostScript" below. In ordinary Acrobat Reader') + c.drawString(100,620, 'we expect to see nothing.') + c.addPostScriptCommand('/Helvetica findfont 48 scalefont setfont 100 400 moveto (Hello PostScript) show') + + + c.drawString(100,500, 'This document also inserts two postscript') + c.drawString(100,480, ' comments at beginning and endof the stream;') + c.drawString(100,460, 'search files for "%PS_BEFORE" and "%PS_AFTER".') + c.addPostScriptCommand('%PS_BEFORE', position=0) + c.addPostScriptCommand('%PS_AFTER', position=2) + + c.save() + + def testTray(self): + "Makes a document with tray command - only works on printers supporting it" + c = Canvas(outputfile('test_pdfbase_postscript_tray.pdf')) + c.setPageCompression(0) + + c.setFont('Helvetica-Bold', 18) + c.drawString(100,700, 'Hello World. This is page 1 of a 2 page document.') + c.drawString(100,680, 'This also has a tray command ("5 setpapertray").') + c.addPostScriptCommand('5 setpapertray') + c.showPage() + + c.setFont('Helvetica-Bold', 16) + c.drawString(100,700, 'Page 2. This should come from a different tray.') + c.drawString(100,680, 'Also, if you print it using a PS device and Acrobat 4/5,') + c.drawString(100,660, 'or export to Postscript, you should see the word') + c.drawString(100,640, '"Hello PostScript" below. In ordinary Acrobat Reader') + c.drawString(100,620, 'we expect to see nothing.') + c.addPostScriptCommand('/Helvetica findfont 48 scalefont setfont 100 400 moveto (Hello PostScript) show') + + + c.save() + +def makeSuite(): + return makeSuiteForClasses(PostScriptTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + print 'saved '+outputfile('test_pdfgen_postscript_visible.pdf') + print 'saved '+outputfile('test_pdfgen_postscript_tray.pdf') + printLocation() diff --git a/bin/reportlab/test/test_pdfbase_ttfonts.py b/bin/reportlab/test/test_pdfbase_ttfonts.py new file mode 100644 index 00000000000..9c228dc8cf1 --- /dev/null +++ b/bin/reportlab/test/test_pdfbase_ttfonts.py @@ -0,0 +1,391 @@ + +"""Test TrueType font subsetting & embedding code. + +This test uses a sample font (luxiserif.ttf) taken from XFree86 which is called Luxi +Serif Regular and is covered under the license in ../fonts/luxiserif_licence.txt. +""" + +import string +from cStringIO import StringIO + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfgen.canvas import Canvas +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.pdfdoc import PDFDocument, PDFError +from reportlab.pdfbase.ttfonts import TTFont, TTFontFace, TTFontFile, TTFOpenFile, \ + TTFontParser, TTFontMaker, TTFError, \ + parse_utf8, makeToUnicodeCMap, \ + FF_SYMBOLIC, FF_NONSYMBOLIC, \ + calcChecksum, add32, _L2U32 + + +def utf8(code): + "Convert a given UCS character index into UTF-8" + if code < 0 or code > 0x7FFFFFFF: + raise ValueError, 'Invalid UCS character 0x%x' % code + elif code < 0x00000080: + return chr(code) + elif code < 0x00000800: + return '%c%c' % \ + (0xC0 + (code >> 6), + 0x80 + (code & 0x3F)) + elif code < 0x00010000: + return '%c%c%c' % \ + (0xE0 + (code >> 12), + 0x80 + ((code >> 6) & 0x3F), + 0x80 + (code & 0x3F)) + elif code < 0x00200000: + return '%c%c%c%c' % \ + (0xF0 + (code >> 18), + 0x80 + ((code >> 12) & 0x3F), + 0x80 + ((code >> 6) & 0x3F), + 0x80 + (code & 0x3F)) + elif code < 0x04000000: + return '%c%c%c%c%c' % \ + (0xF8 + (code >> 24), + 0x80 + ((code >> 18) & 0x3F), + 0x80 + ((code >> 12) & 0x3F), + 0x80 + ((code >> 6) & 0x3F), + 0x80 + (code & 0x3F)) + else: + return '%c%c%c%c%c%c' % \ + (0xFC + (code >> 30), + 0x80 + ((code >> 24) & 0x3F), + 0x80 + ((code >> 18) & 0x3F), + 0x80 + ((code >> 12) & 0x3F), + 0x80 + ((code >> 6) & 0x3F), + 0x80 + (code & 0x3F)) + +def _simple_subset_generation(fn,npages,alter=0): + c = Canvas(outputfile(fn)) + c.setFont('Helvetica', 30) + c.drawString(100,700, 'Unicode TrueType Font Test %d pages' % npages) + # Draw a table of Unicode characters + for p in xrange(npages): + for fontName in ('TestFont','RinaFont'): + c.setFont(fontName, 10) + for i in xrange(32): + for j in xrange(32): + ch = utf8(i * 32 + j+p*alter) + c.drawString(80 + j * 13 + int(j / 16) * 4, 600 - i * 13 - int(i / 8) * 8, ch) + c.showPage() + c.save() + +class TTFontsTestCase(unittest.TestCase): + "Make documents with TrueType fonts" + + def testTTF(self): + "Test PDF generation with TrueType fonts" + pdfmetrics.registerFont(TTFont("TestFont", "luxiserif.ttf")) + pdfmetrics.registerFont(TTFont("RinaFont", "rina.ttf")) + _simple_subset_generation('test_pdfbase_ttfonts1.pdf',1) + _simple_subset_generation('test_pdfbase_ttfonts3.pdf',3) + _simple_subset_generation('test_pdfbase_ttfonts35.pdf',3,5) + + # Do it twice with the same font object + c = Canvas(outputfile('test_pdfbase_ttfontsadditional.pdf')) + # Draw a table of Unicode characters + c.setFont('TestFont', 10) + c.drawString(100, 700, 'Hello, ' + utf8(0xffee)) + c.save() + + +class TTFontFileTestCase(unittest.TestCase): + "Tests TTFontFile, TTFontParser and TTFontMaker classes" + + def testFontFileFailures(self): + "Tests TTFontFile constructor error checks" + self.assertRaises(TTFError, TTFontFile, "nonexistent file") + self.assertRaises(TTFError, TTFontFile, StringIO("")) + self.assertRaises(TTFError, TTFontFile, StringIO("invalid signature")) + self.assertRaises(TTFError, TTFontFile, StringIO("OTTO - OpenType not supported yet")) + self.assertRaises(TTFError, TTFontFile, StringIO("\0\1\0\0")) + + def testFontFileReads(self): + "Tests TTFontParset.read_xxx" + + class FakeTTFontFile(TTFontParser): + def __init__(self, data): + self._ttf_data = data + self._pos = 0 + + ttf = FakeTTFontFile("\x81\x02\x03\x04" "\x85\x06" "ABCD" "\x7F\xFF" "\x80\x00" "\xFF\xFF") + self.assertEquals(ttf.read_ulong(), _L2U32(0x81020304L)) # big-endian + self.assertEquals(ttf._pos, 4) + self.assertEquals(ttf.read_ushort(), 0x8506) + self.assertEquals(ttf._pos, 6) + self.assertEquals(ttf.read_tag(), 'ABCD') + self.assertEquals(ttf._pos, 10) + self.assertEquals(ttf.read_short(), 0x7FFF) + self.assertEquals(ttf.read_short(), -0x8000) + self.assertEquals(ttf.read_short(), -1) + + def testFontFile(self): + "Tests TTFontFile and TTF parsing code" + ttf = TTFontFile("luxiserif.ttf") + self.assertEquals(ttf.name, "LuxiSerif") + self.assertEquals(ttf.flags, FF_SYMBOLIC) + self.assertEquals(ttf.italicAngle, 0.0) + self.assertEquals(ttf.ascent, 783) # FIXME: or 992? + self.assertEquals(ttf.descent, -206) # FIXME: or -210? + self.assertEquals(ttf.capHeight, 0) + self.assertEquals(ttf.bbox, [-204, -211, 983, 992]) + self.assertEquals(ttf.stemV, 87) + self.assertEquals(ttf.defaultWidth, 250) + + def testAdd32(self): + "Test add32" + self.assertEquals(add32(10, -6), 4) + self.assertEquals(add32(6, -10), -4) + self.assertEquals(add32(_L2U32(0x80000000L), -1), 0x7FFFFFFF) + self.assertEquals(add32(0x7FFFFFFF, 1), _L2U32(0x80000000L)) + + def testChecksum(self): + "Test calcChecksum function" + self.assertEquals(calcChecksum(""), 0) + self.assertEquals(calcChecksum("\1"), 0x01000000) + self.assertEquals(calcChecksum("\x01\x02\x03\x04\x10\x20\x30\x40"), 0x11223344) + self.assertEquals(calcChecksum("\x81"), _L2U32(0x81000000L)) + self.assertEquals(calcChecksum("\x81\x02"), _L2U32(0x81020000L)) + self.assertEquals(calcChecksum("\x81\x02\x03"), _L2U32(0x81020300L)) + self.assertEquals(calcChecksum("\x81\x02\x03\x04"), _L2U32(0x81020304L)) + self.assertEquals(calcChecksum("\x81\x02\x03\x04\x05"), _L2U32(0x86020304L)) + self.assertEquals(calcChecksum("\x41\x02\x03\x04\xD0\x20\x30\x40"), 0x11223344) + self.assertEquals(calcChecksum("\xD1\x02\x03\x04\x40\x20\x30\x40"), 0x11223344) + self.assertEquals(calcChecksum("\x81\x02\x03\x04\x90\x20\x30\x40"), 0x11223344) + self.assertEquals(calcChecksum("\x7F\xFF\xFF\xFF\x00\x00\x00\x01"), _L2U32(0x80000000L)) + + def testFontFileChecksum(self): + "Tests TTFontFile and TTF parsing code" + file = TTFOpenFile("luxiserif.ttf")[1].read() + TTFontFile(StringIO(file), validate=1) # should not fail + file1 = file[:12345] + "\xFF" + file[12346:] # change one byte + self.assertRaises(TTFError, TTFontFile, StringIO(file1), validate=1) + file1 = file[:8] + "\xFF" + file[9:] # change one byte + self.assertRaises(TTFError, TTFontFile, StringIO(file1), validate=1) + + def testSubsetting(self): + "Tests TTFontFile and TTF parsing code" + ttf = TTFontFile("luxiserif.ttf") + subset = ttf.makeSubset([0x41, 0x42]) + subset = TTFontFile(StringIO(subset), 0) + for tag in ('cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'OS/2', + 'post', 'cvt ', 'fpgm', 'glyf', 'loca', 'prep'): + self.assert_(subset.get_table(tag)) + + subset.seek_table('loca') + for n in range(4): + pos = subset.read_ushort() # this is actually offset / 2 + self.failIf(pos % 2 != 0, "glyph %d at +%d should be long aligned" % (n, pos * 2)) + + self.assertEquals(subset.name, "LuxiSerif") + self.assertEquals(subset.flags, FF_SYMBOLIC) + self.assertEquals(subset.italicAngle, 0.0) + self.assertEquals(subset.ascent, 783) # FIXME: or 992? + self.assertEquals(subset.descent, -206) # FIXME: or -210? + self.assertEquals(subset.capHeight, 0) + self.assertEquals(subset.bbox, [-204, -211, 983, 992]) + self.assertEquals(subset.stemV, 87) + + def testFontMaker(self): + "Tests TTFontMaker class" + ttf = TTFontMaker() + ttf.add("ABCD", "xyzzy") + ttf.add("QUUX", "123") + ttf.add("head", "12345678xxxx") + stm = ttf.makeStream() + ttf = TTFontParser(StringIO(stm), 0) + self.assertEquals(ttf.get_table("ABCD"), "xyzzy") + self.assertEquals(ttf.get_table("QUUX"), "123") + + +class TTFontFaceTestCase(unittest.TestCase): + "Tests TTFontFace class" + + def testAddSubsetObjects(self): + "Tests TTFontFace.addSubsetObjects" + face = TTFontFace("luxiserif.ttf") + doc = PDFDocument() + fontDescriptor = face.addSubsetObjects(doc, "TestFont", [ 0x78, 0x2017 ]) + fontDescriptor = doc.idToObject[fontDescriptor.name].dict + self.assertEquals(fontDescriptor['Type'], '/FontDescriptor') + self.assertEquals(fontDescriptor['Ascent'], face.ascent) + self.assertEquals(fontDescriptor['CapHeight'], face.capHeight) + self.assertEquals(fontDescriptor['Descent'], face.descent) + self.assertEquals(fontDescriptor['Flags'], (face.flags & ~FF_NONSYMBOLIC) | FF_SYMBOLIC) + self.assertEquals(fontDescriptor['FontName'], "/TestFont") + self.assertEquals(fontDescriptor['FontBBox'].sequence, face.bbox) + self.assertEquals(fontDescriptor['ItalicAngle'], face.italicAngle) + self.assertEquals(fontDescriptor['StemV'], face.stemV) + fontFile = fontDescriptor['FontFile2'] + fontFile = doc.idToObject[fontFile.name] + self.assert_(fontFile.content != "") + + +class TTFontTestCase(unittest.TestCase): + "Tests TTFont class" + + def testParseUTF8(self): + "Tests parse_utf8" + self.assertEquals(parse_utf8(""), []) + for i in range(0, 0x80): + self.assertEquals(parse_utf8(chr(i)), [i]) + for i in range(0x80, 0xA0): + self.assertRaises(ValueError, parse_utf8, chr(i)) + self.assertEquals(parse_utf8("abc"), [0x61, 0x62, 0x63]) + self.assertEquals(parse_utf8("\xC2\xA9x"), [0xA9, 0x78]) + self.assertEquals(parse_utf8("\xE2\x89\xA0x"), [0x2260, 0x78]) + self.assertRaises(ValueError, parse_utf8, "\xE2\x89x") + # for i in range(0, 0xFFFF): - overkill + for i in range(0x80, 0x200) + range(0x300, 0x400) + [0xFFFE, 0xFFFF]: + self.assertEquals(parse_utf8(utf8(i)), [i]) + + def testStringWidth(self): + "Test TTFont.stringWidth" + font = TTFont("TestFont", "luxiserif.ttf") + self.assert_(font.stringWidth("test", 10) > 0) + width = font.stringWidth(utf8(0x2260) * 2, 1000) + expected = font.face.getCharWidth(0x2260) * 2 + self.assert_(abs(width - expected) < 0.01, "%g != %g" % (width, expected)) + + def testSplitString(self): + "Tests TTFont.splitString" + doc = PDFDocument() + font = TTFont("TestFont", "luxiserif.ttf") + text = string.join(map(utf8, xrange(0, 511)), "") + allchars = string.join(map(chr, xrange(0, 256)), "") + nospace = allchars[:32] + allchars[33:] + chunks = [(0, allchars), (1, nospace)] + self.assertEquals(font.splitString(text, doc), chunks) + # Do it twice + self.assertEquals(font.splitString(text, doc), chunks) + + text = string.join(map(utf8, range(510, -1, -1)), "") + allchars = string.join(map(chr, range(255, -1, -1)), "") + nospace = allchars[:223] + allchars[224:] + chunks = [(1, nospace), (0, allchars)] + self.assertEquals(font.splitString(text, doc), chunks) + + def testSplitStringSpaces(self): + # In order for justification (word spacing) to work, the space + # glyph must have a code 32, and no other character should have + # that code in any subset, or word spacing will be applied to it. + + doc = PDFDocument() + font = TTFont("TestFont", "luxiserif.ttf") + text = string.join(map(utf8, range(512, -1, -1)), "") + chunks = font.splitString(text, doc) + state = font.state[doc] + self.assertEquals(state.assignments[32], 32) + self.assertEquals(state.subsets[0][32], 32) + self.assertEquals(state.subsets[1][32], 32) + + def testSubsetInternalName(self): + "Tests TTFont.getSubsetInternalName" + doc = PDFDocument() + font = TTFont("TestFont", "luxiserif.ttf") + # Actually generate some subsets + text = string.join(map(utf8, range(0, 513)), "") + font.splitString(text, doc) + self.assertRaises(IndexError, font.getSubsetInternalName, -1, doc) + self.assertRaises(IndexError, font.getSubsetInternalName, 3, doc) + self.assertEquals(font.getSubsetInternalName(0, doc), "/F1+0") + self.assertEquals(font.getSubsetInternalName(1, doc), "/F1+1") + self.assertEquals(font.getSubsetInternalName(2, doc), "/F1+2") + self.assertEquals(doc.delayedFonts, [font]) + + def testAddObjectsEmpty(self): + "TTFont.addObjects should not fail when no characters were used" + font = TTFont("TestFont", "luxiserif.ttf") + doc = PDFDocument() + font.addObjects(doc) + + def no_longer_testAddObjectsResets(self): + "Test that TTFont.addObjects resets the font" + # Actually generate some subsets + doc = PDFDocument() + font = TTFont("TestFont", "luxiserif.ttf") + font.splitString('a', doc) # create some subset + doc = PDFDocument() + font.addObjects(doc) + self.assertEquals(font.frozen, 0) + self.assertEquals(font.nextCode, 0) + self.assertEquals(font.subsets, []) + self.assertEquals(font.assignments, {}) + font.splitString('ba', doc) # should work + + def testParallelConstruction(self): + "Test that TTFont can be used for different documents at the same time" + doc1 = PDFDocument() + doc2 = PDFDocument() + font = TTFont("TestFont", "luxiserif.ttf") + self.assertEquals(font.splitString(u'hello ', doc1), [(0, 'hello ')]) + self.assertEquals(font.splitString(u'hello ', doc2), [(0, 'hello ')]) + self.assertEquals(font.splitString(u'\u0410\u0411'.encode('UTF-8'), doc1), [(0, '\x80\x81')]) + self.assertEquals(font.splitString(u'\u0412'.encode('UTF-8'), doc2), [(0, '\x80')]) + font.addObjects(doc1) + self.assertEquals(font.splitString(u'\u0413'.encode('UTF-8'), doc2), [(0, '\x81')]) + font.addObjects(doc2) + + def testAddObjects(self): + "Test TTFont.addObjects" + # Actually generate some subsets + doc = PDFDocument() + font = TTFont("TestFont", "luxiserif.ttf") + font.splitString('a', doc) # create some subset + internalName = font.getSubsetInternalName(0, doc)[1:] + font.addObjects(doc) + pdfFont = doc.idToObject[internalName] + self.assertEquals(doc.idToObject['BasicFonts'].dict[internalName], pdfFont) + self.assertEquals(pdfFont.Name, internalName) + self.assertEquals(pdfFont.BaseFont, "AAAAAA+LuxiSerif") + self.assertEquals(pdfFont.FirstChar, 0) + self.assertEquals(pdfFont.LastChar, 127) + self.assertEquals(len(pdfFont.Widths.sequence), 128) + toUnicode = doc.idToObject[pdfFont.ToUnicode.name] + self.assert_(toUnicode.content != "") + fontDescriptor = doc.idToObject[pdfFont.FontDescriptor.name] + self.assertEquals(fontDescriptor.dict['Type'], '/FontDescriptor') + + def testMakeToUnicodeCMap(self): + "Test makeToUnicodeCMap" + self.assertEquals(makeToUnicodeCMap("TestFont", [ 0x1234, 0x4321, 0x4242 ]), +"""/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TestFont) +/Ordering (TestFont) +/Supplement 0 +>> def +/CMapName /TestFont def +/CMapType 2 def +1 begincodespacerange +<00> <02> +endcodespacerange +3 beginbfchar +<00> <1234> +<01> <4321> +<02> <4242> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end""") + + +def makeSuite(): + suite = makeSuiteForClasses( + TTFontsTestCase, + TTFontFileTestCase, + TTFontFaceTestCase, + TTFontTestCase) + return suite + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_pdfgen_callback.py b/bin/reportlab/test/test_pdfgen_callback.py new file mode 100644 index 00000000000..a4e3e280c74 --- /dev/null +++ b/bin/reportlab/test/test_pdfgen_callback.py @@ -0,0 +1,39 @@ +#!/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/test/test_pdfgen_callback.py +__version__=''' $Id: test_pdfgen_callback.py 2619 2005-06-24 14:49:15Z rgbecker $ ''' +__doc__='checks callbacks work' + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfgen.canvas import Canvas +from reportlab.test.test_pdfgen_general import makeDocument + +_PAGE_COUNT = 0 + + +class CallBackTestCase(unittest.TestCase): + "checks it gets called" + + def callMe(self, pageNo): + self.pageCount = pageNo + + def test0(self): + "Make a PDFgen document with most graphics features" + + self.pageCount = 0 + makeDocument(outputfile('test_pdfgen_callback.pdf'), pageCallBack=self.callMe) + #no point saving it! + assert self.pageCount >= 7, 'page count not called!' + + +def makeSuite(): + return makeSuiteForClasses(CallBackTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_pdfgen_general.py b/bin/reportlab/test/test_pdfgen_general.py new file mode 100644 index 00000000000..d20f0ee2f8f --- /dev/null +++ b/bin/reportlab/test/test_pdfgen_general.py @@ -0,0 +1,840 @@ +#!/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/test/test_pdfgen_general.py +__version__=''' $Id: test_pdfgen_general.py 2852 2006-05-08 15:04:15Z rgbecker $ ''' +__doc__='testscript for reportlab.pdfgen' +#tests and documents new low-level canvas + +import os, string + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfgen import canvas # gmcm 2000/10/13, pdfgen now a package +from reportlab.lib.units import inch, cm +from reportlab.lib import colors +from reportlab.lib.utils import haveImages + +################################################################# +# +# first some drawing utilities +# +# +################################################################ + +BASEFONT = ('Times-Roman', 10) +def framePageForm(c): + c.beginForm("frame") + c.saveState() + # forms can't do non-constant operations + #canvas.setFont('Times-BoldItalic',20) + #canvas.drawString(inch, 10.5 * inch, title) + + #c.setFont('Times-Roman',10) + #c.drawCentredString(4.135 * inch, 0.75 * inch, + # 'Page %d' % c.getPageNumber()) + + #draw a border + c.setFillColor(colors.ReportLabBlue) + c.rect(0.3*inch, inch, 0.5*inch, 10*inch, fill=1) + from reportlab.lib import corp + c.translate(0.8*inch, 9.6*inch) + c.rotate(90) + logo = corp.ReportLabLogo(width=1.3*inch, height=0.5*inch, powered_by=1) + c.setFillColorRGB(1,1,1) + c.setStrokeColorRGB(1,1,1) + logo.draw(c) + #c.setStrokeColorRGB(1,0,0) + #c.setLineWidth(5) + #c.line(0.8 * inch, inch, 0.8 * inch, 10.75 * inch) + #reset carefully afterwards + #canvas.setLineWidth(1) + #canvas.setStrokeColorRGB(0,0,0)\ + c.restoreState() + c.endForm() + +def framePage(canvas, title): + global closeit + titlelist.append(title) + #canvas._inPage0() # do we need this at all? would be good to eliminate it + canvas.saveState() + canvas.setFont('Times-BoldItalic',20) + + canvas.drawString(inch, 10.5 * inch, title) + canvas.bookmarkHorizontalAbsolute(title, 10.8*inch) + #newsection(title) + canvas.addOutlineEntry(title+" section", title, level=0, closed=closeit) + closeit = not closeit # close every other one + canvas.setFont('Times-Roman',10) + canvas.drawCentredString(4.135 * inch, 0.75 * inch, + 'Page %d' % canvas.getPageNumber()) + canvas.restoreState() + canvas.doForm("frame") + + +def makesubsection(canvas, title, horizontal): + canvas.bookmarkHorizontalAbsolute(title, horizontal) + #newsubsection(title) + canvas.addOutlineEntry(title+" subsection", title, level=1) + + +# outline helpers +#outlinenametree = [] +#def newsection(name): +# outlinenametree.append(name) + + +#def newsubsection(name): +# from types import TupleType +# thissection = outlinenametree[-1] +# if type(thissection) is not TupleType: +# subsectionlist = [] +# thissection = outlinenametree[-1] = (thissection, subsectionlist) +# else: +# (sectionname, subsectionlist) = thissection +# subsectionlist.append(name) + + +class DocBlock: + """A DocBlock has a chunk of commentary and a chunk of code. + It prints the code and commentary, then executes the code, + which is presumed to draw in a region reserved for it. + """ + def __init__(self): + self.comment1 = "A doc block" + self.code = "canvas.setTextOrigin(cm, cm)\ncanvas.textOut('Hello World')" + self.comment2 = "That was a doc block" + self.drawHeight = 0 + + def _getHeight(self): + "splits into lines" + self.comment1lines = string.split(self.comment1, '\n') + self.codelines = string.split(self.code, '\n') + self.comment2lines = string.split(self.comment2, '\n') + textheight = (len(self.comment1lines) + + len(self.code) + + len(self.comment2lines) + + 18) + return max(textheight, self.drawHeight) + + def draw(self, canvas, x, y): + #specifies top left corner + canvas.saveState() + height = self._getHeight() + canvas.rect(x, y-height, 6*inch, height) + #first draw the text + canvas.setTextOrigin(x + 3 * inch, y - 12) + canvas.setFont('Times-Roman',10) + canvas.textLines(self.comment1) + drawCode(canvas, self.code) + canvas.textLines(self.comment2) + + #now a box for the drawing, slightly within rect + canvas.rect(x + 9, y - height + 9, 198, height - 18) + #boundary: + self.namespace = {'canvas':canvas,'cm': cm,'inch':inch} + canvas.translate(x+9, y - height + 9) + codeObj = compile(self.code, '','exec') + exec codeObj in self.namespace + + canvas.restoreState() + + +def drawAxes(canvas, label): + """draws a couple of little rulers showing the coords - + uses points as units so you get an imperial ruler + one inch on each side""" + #y axis + canvas.line(0,0,0,72) + for y in range(9): + tenths = (y+1) * 7.2 + canvas.line(-6,tenths,0,tenths) + canvas.line(-6, 66, 0, 72) #arrow... + canvas.line(6, 66, 0, 72) #arrow... + + canvas.line(0,0,72,0) + for x in range(9): + tenths = (x+1) * 7.2 + canvas.line(tenths,-6,tenths, 0) + canvas.line(66, -6, 72, 0) #arrow... + canvas.line(66, +6, 72, 0) #arrow... + + canvas.drawString(18, 30, label) + + +def drawCrossHairs(canvas, x, y): + """just a marker for checking text metrics - blue for fun""" + + canvas.saveState() + canvas.setStrokeColorRGB(0,1,0) + canvas.line(x-6,y,x+6,y) + canvas.line(x,y-6,x,y+6) + canvas.restoreState() + + +def drawCode(canvas, code): + """Draws a block of text at current point, indented and in Courier""" + canvas.addLiteral('36 0 Td') + canvas.setFillColor(colors.blue) + canvas.setFont('Courier',10) + + t = canvas.beginText() + t.textLines(code) + c.drawText(t) + + canvas.setFillColor(colors.black) + canvas.addLiteral('-36 0 Td') + canvas.setFont('Times-Roman',10) + + +def makeDocument(filename, pageCallBack=None): + #the extra arg is a hack added later, so other + #tests can get hold of the canvas just before it is + #saved + global titlelist, closeit + titlelist = [] + closeit = 0 + + c = canvas.Canvas(filename) + c.setPageCompression(0) + c.setPageCallBack(pageCallBack) + framePageForm(c) # define the frame form + c.showOutline() + + framePage(c, 'PDFgen graphics API test script') + makesubsection(c, "PDFgen", 10*inch) + + #quickie encoding test: when canvas encoding not set, + #the following should do (tm), (r) and (c) + msg_uni = u'copyright\u00A9 trademark\u2122 registered\u00AE ReportLab in unicode!' + msg_utf8 = msg_uni.replace('unicode','utf8').encode('utf8') + c.drawString(100, 100, msg_uni) + c.drawString(100, 80, msg_utf8) + + + + + t = c.beginText(inch, 10*inch) + t.setFont('Times-Roman', 10) + drawCrossHairs(c, t.getX(),t.getY()) + t.textLines(""" +The ReportLab library permits you to create PDF documents directly from +your Python code. The "pdfgen" subpackage is the lowest level exposed +to the user and lets you directly position test and graphics on the +page, with access to almost the full range of PDF features. + The API is intended to closely mirror the PDF / Postscript imaging +model. There is an almost one to one correspondence between commands +and PDF operators. However, where PDF provides several ways to do a job, +we have generally only picked one. + The test script attempts to use all of the methods exposed by the Canvas +class, defined in reportlab/pdfgen/canvas.py + First, let's look at text output. There are some basic commands +to draw strings: +- canvas.setFont(fontname, fontsize [, leading]) +- canvas.drawString(x, y, text) +- canvas.drawRightString(x, y, text) +- canvas.drawCentredString(x, y, text) + +The coordinates are in points starting at the bottom left corner of the +page. When setting a font, the leading (i.e. inter-line spacing) +defaults to 1.2 * fontsize if the fontsize is not provided. + +For more sophisticated operations, you can create a Text Object, defined +in reportlab/pdfgen/testobject.py. Text objects produce tighter PDF, run +faster and have many methods for precise control of spacing and position. +Basic usage goes as follows: +- tx = canvas.beginText(x, y) +- tx.textOut('Hello') # this moves the cursor to the right +- tx.textLine('Hello again') # prints a line and moves down +- y = tx.getY() # getX, getY and getCursor track position +- canvas.drawText(tx) # all gets drawn at the end + +The green crosshairs below test whether the text cursor is working +properly. They should appear at the bottom left of each relevant +substring. +""") + + t.setFillColorRGB(1,0,0) + t.setTextOrigin(inch, 4*inch) + drawCrossHairs(c, t.getX(),t.getY()) + t.textOut('textOut moves across:') + drawCrossHairs(c, t.getX(),t.getY()) + t.textOut('textOut moves across:') + drawCrossHairs(c, t.getX(),t.getY()) + t.textOut('textOut moves across:') + drawCrossHairs(c, t.getX(),t.getY()) + t.textLine('') + drawCrossHairs(c, t.getX(),t.getY()) + t.textLine('textLine moves down') + drawCrossHairs(c, t.getX(),t.getY()) + t.textLine('textLine moves down') + drawCrossHairs(c, t.getX(),t.getY()) + t.textLine('textLine moves down') + drawCrossHairs(c, t.getX(),t.getY()) + + t.setTextOrigin(4*inch,3.25*inch) + drawCrossHairs(c, t.getX(),t.getY()) + t.textLines('This is a multi-line\nstring with embedded newlines\ndrawn with textLines().\n') + drawCrossHairs(c, t.getX(),t.getY()) + t.textLines(['This is a list of strings', + 'drawn with textLines().']) + c.drawText(t) + + t = c.beginText(2*inch,2*inch) + t.setFont('Times-Roman',10) + drawCrossHairs(c, t.getX(),t.getY()) + t.textOut('Small text.') + drawCrossHairs(c, t.getX(),t.getY()) + t.setFont('Courier',14) + t.textOut('Bigger fixed width text.') + drawCrossHairs(c, t.getX(),t.getY()) + t.setFont('Times-Roman',10) + t.textOut('Small text again.') + drawCrossHairs(c, t.getX(),t.getY()) + c.drawText(t) + + #try out the decimal tabs high on the right. + c.setStrokeColor(colors.silver) + c.line(7*inch, 6*inch, 7*inch, 4.5*inch) + + c.setFillColor(colors.black) + c.setFont('Times-Roman',10) + c.drawString(6*inch, 6.2*inch, "Testing decimal alignment") + c.drawString(6*inch, 6.05*inch, "- aim for silver line") + c.line(7*inch, 6*inch, 7*inch, 4.5*inch) + + c.drawAlignedString(7*inch, 5.8*inch, "1,234,567.89") + c.drawAlignedString(7*inch, 5.6*inch, "3,456.789") + c.drawAlignedString(7*inch, 5.4*inch, "123") + c.setFillColor(colors.red) + c.drawAlignedString(7*inch, 5.2*inch, "(7,192,302.30)") + + #mark the cursor where it stopped + c.showPage() + + + ############################################################## + # + # page 2 - line styles + # + ############################################################### + + #page 2 - lines and styles + framePage(c, 'Line Drawing Styles') + + + + # three line ends, lines drawn the hard way + #firt make some vertical end markers + c.setDash(4,4) + c.setLineWidth(0) + c.line(inch,9.2*inch,inch, 7.8*inch) + c.line(3*inch,9.2*inch,3*inch, 7.8*inch) + c.setDash() #clears it + + c.setLineWidth(5) + c.setLineCap(0) + p = c.beginPath() + p.moveTo(inch, 9*inch) + p.lineTo(3*inch, 9*inch) + c.drawPath(p) + c.drawString(4*inch, 9*inch, 'the default - butt caps project half a width') + makesubsection(c, "caps and joins", 8.5*inch) + + c.setLineCap(1) + p = c.beginPath() + p.moveTo(inch, 8.5*inch) + p.lineTo(3*inch, 8.5*inch) + c.drawPath(p) + c.drawString(4*inch, 8.5*inch, 'round caps') + + c.setLineCap(2) + p = c.beginPath() + p.moveTo(inch, 8*inch) + p.lineTo(3*inch, 8*inch) + c.drawPath(p) + c.drawString(4*inch, 8*inch, 'square caps') + + c.setLineCap(0) + + # three line joins + c.setLineJoin(0) + p = c.beginPath() + p.moveTo(inch, 7*inch) + p.lineTo(2*inch, 7*inch) + p.lineTo(inch, 6.7*inch) + c.drawPath(p) + c.drawString(4*inch, 6.8*inch, 'Default - mitered join') + + c.setLineJoin(1) + p = c.beginPath() + p.moveTo(inch, 6.5*inch) + p.lineTo(2*inch, 6.5*inch) + p.lineTo(inch, 6.2*inch) + c.drawPath(p) + c.drawString(4*inch, 6.3*inch, 'round join') + + c.setLineJoin(2) + p = c.beginPath() + p.moveTo(inch, 6*inch) + p.lineTo(2*inch, 6*inch) + p.lineTo(inch, 5.7*inch) + c.drawPath(p) + c.drawString(4*inch, 5.8*inch, 'bevel join') + + c.setDash(6,6) + p = c.beginPath() + p.moveTo(inch, 5*inch) + p.lineTo(3*inch, 5*inch) + c.drawPath(p) + c.drawString(4*inch, 5*inch, 'dash 6 points on, 6 off- setDash(6,6) setLineCap(0)') + makesubsection(c, "dash patterns", 5*inch) + + c.setLineCap(1) + p = c.beginPath() + p.moveTo(inch, 4.5*inch) + p.lineTo(3*inch, 4.5*inch) + c.drawPath(p) + c.drawString(4*inch, 4.5*inch, 'dash 6 points on, 6 off- setDash(6,6) setLineCap(1)') + + c.setLineCap(0) + c.setDash([1,2,3,4,5,6],0) + p = c.beginPath() + p.moveTo(inch, 4.0*inch) + p.lineTo(3*inch, 4.0*inch) + c.drawPath(p) + c.drawString(4*inch, 4*inch, 'dash growing - setDash([1,2,3,4,5,6],0) setLineCap(0)') + + c.setLineCap(1) + c.setLineJoin(1) + c.setDash(32,12) + p = c.beginPath() + p.moveTo(inch, 3.0*inch) + p.lineTo(2.5*inch, 3.0*inch) + p.lineTo(inch, 2*inch) + c.drawPath(p) + c.drawString(4*inch, 3*inch, 'dash pattern, join and cap style interacting - ') + c.drawString(4*inch, 3*inch - 12, 'round join & miter results in sausages') + c.textAnnotation('Annotation',Rect=(4*inch, 3*inch-72, inch,inch-12)) + + c.showPage() + + +############################################################## +# +# higher level shapes +# +############################################################### + framePage(c, 'Shape Drawing Routines') + + t = c.beginText(inch, 10*inch) + t.textLines(""" +Rather than making your own paths, you have access to a range of shape routines. +These are built in pdfgen out of lines and bezier curves, but use the most compact +set of operators possible. We can add any new ones that are of general use at no +cost to performance.""") + t.textLine() + + #line demo + makesubsection(c, "lines", 10*inch) + c.line(inch, 8*inch, 3*inch, 8*inch) + t.setTextOrigin(4*inch, 8*inch) + t.textLine('canvas.line(x1, y1, x2, y2)') + + #bezier demo - show control points + makesubsection(c, "bezier curves", 7.5*inch) + (x1, y1, x2, y2, x3, y3, x4, y4) = ( + inch, 6.5*inch, + 1.2*inch, 7.5 * inch, + 3*inch, 7.5 * inch, + 3.5*inch, 6.75 * inch + ) + c.bezier(x1, y1, x2, y2, x3, y3, x4, y4) + c.setDash(3,3) + c.line(x1,y1,x2,y2) + c.line(x3,y3,x4,y4) + c.setDash() + t.setTextOrigin(4*inch, 7 * inch) + t.textLine('canvas.bezier(x1, y1, x2, y2, x3, y3, x4, y4)') + + #rectangle + makesubsection(c, "rectangles", 7*inch) + c.rect(inch, 5.25 * inch, 2 * inch, 0.75 * inch) + t.setTextOrigin(4*inch, 5.5 * inch) + t.textLine('canvas.rect(x, y, width, height) - x,y is lower left') + + #wedge + makesubsection(c, "wedges", 5*inch) + c.wedge(inch, 5*inch, 3*inch, 4*inch, 0, 315) + t.setTextOrigin(4*inch, 4.5 * inch) + t.textLine('canvas.wedge(x1, y1, x2, y2, startDeg, extentDeg)') + t.textLine('Note that this is an elliptical arc, not just circular!') + + #wedge the other way + c.wedge(inch, 4*inch, 3*inch, 3*inch, 0, -45) + t.setTextOrigin(4*inch, 3.5 * inch) + t.textLine('Use a negative extent to go clockwise') + + #circle + makesubsection(c, "circles", 3.5*inch) + c.circle(1.5*inch, 2*inch, 0.5 * inch) + c.circle(3*inch, 2*inch, 0.5 * inch) + t.setTextOrigin(4*inch, 2 * inch) + t.textLine('canvas.circle(x, y, radius)') + c.drawText(t) + + c.showPage() + +############################################################## +# +# Page 4 - fonts +# +############################################################### + framePage(c, "Font Control") + + c.drawString(inch, 10*inch, 'Listing available fonts...') + + y = 9.5*inch + for fontname in c.getAvailableFonts(): + c.setFont(fontname,24) + c.drawString(inch, y, 'This should be %s' % fontname) + y = y - 28 + makesubsection(c, "fonts and colors", 4*inch) + + c.setFont('Times-Roman', 12) + t = c.beginText(inch, 4*inch) + t.textLines("""Now we'll look at the color functions and how they interact + with the text. In theory, a word is just a shape; so setFillColorRGB() + determines most of what you see. If you specify other text rendering + modes, an outline color could be defined by setStrokeColorRGB() too""") + c.drawText(t) + + t = c.beginText(inch, 2.75 * inch) + t.setFont('Times-Bold',36) + t.setFillColor(colors.green) #green + t.textLine('Green fill, no stroke') + + #t.setStrokeColorRGB(1,0,0) #ou can do this in a text object, or the canvas. + t.setStrokeColor(colors.red) #ou can do this in a text object, or the canvas. + t.setTextRenderMode(2) # fill and stroke + t.textLine('Green fill, red stroke - yuk!') + + t.setTextRenderMode(0) # back to default - fill only + t.setFillColorRGB(0,0,0) #back to default + t.setStrokeColorRGB(0,0,0) #ditto + c.drawText(t) + c.showPage() + +######################################################################### +# +# Page 5 - coord transforms +# +######################################################################### + framePage(c, "Coordinate Transforms") + c.setFont('Times-Roman', 12) + t = c.beginText(inch, 10 * inch) + t.textLines("""This shows coordinate transformations. We draw a set of axes, + moving down the page and transforming space before each one. + You can use saveState() and restoreState() to unroll transformations. + Note that functions which track the text cursor give the cursor position + in the current coordinate system; so if you set up a 6 inch high frame + 2 inches down the page to draw text in, and move the origin to its top + left, you should stop writing text after six inches and not eight.""") + c.drawText(t) + + drawAxes(c, "0. at origin") + c.addLiteral('%about to translate space') + c.translate(2*inch, 7 * inch) + drawAxes(c, '1. translate near top of page') + + c.saveState() + c.translate(1*inch, -2 * inch) + drawAxes(c, '2. down 2 inches, across 1') + c.restoreState() + + c.saveState() + c.translate(0, -3 * inch) + c.scale(2, -1) + drawAxes(c, '3. down 3 from top, scale (2, -1)') + c.restoreState() + + c.saveState() + c.translate(0, -5 * inch) + c.rotate(-30) + drawAxes(c, "4. down 5, rotate 30' anticlockwise") + c.restoreState() + + c.saveState() + c.translate(3 * inch, -5 * inch) + c.skew(0,30) + drawAxes(c, "5. down 5, 3 across, skew beta 30") + c.restoreState() + + c.showPage() + +######################################################################### +# +# Page 6 - clipping +# +######################################################################### + framePage(c, "Clipping") + c.setFont('Times-Roman', 12) + t = c.beginText(inch, 10 * inch) + t.textLines("""This shows clipping at work. We draw a chequerboard of rectangles + into a path object, and clip it. This then forms a mask which limits the region of + the page on which one can draw. This paragraph was drawn after setting the clipping + path, and so you should only see part of the text.""") + c.drawText(t) + + c.saveState() + #c.setFillColorRGB(0,0,1) + p = c.beginPath() + #make a chesboard effect, 1 cm squares + for i in range(14): + x0 = (3 + i) * cm + for j in range(7): + y0 = (16 + j) * cm + p.rect(x0, y0, 0.85*cm, 0.85*cm) + c.addLiteral('%Begin clip path') + c.clipPath(p) + c.addLiteral('%End clip path') + t = c.beginText(3 * cm, 22.5 * cm) + t.textLines("""This shows clipping at work. We draw a chequerboard of rectangles + into a path object, and clip it. This then forms a mask which limits the region of + the page on which one can draw. This paragraph was drawn after setting the clipping + path, and so you should only see part of the text. + This shows clipping at work. We draw a chequerboard of rectangles + into a path object, and clip it. This then forms a mask which limits the region of + the page on which one can draw. This paragraph was drawn after setting the clipping + path, and so you should only see part of the text. + This shows clipping at work. We draw a chequerboard of rectangles + into a path object, and clip it. This then forms a mask which limits the region of + the page on which one can draw. This paragraph was drawn after setting the clipping + path, and so you should only see part of the text.""") + c.drawText(t) + + c.restoreState() + + t = c.beginText(inch, 5 * inch) + t.textLines("""You can also use text as an outline for clipping with the text render mode. + The API is not particularly clean on this and one has to follow the right sequence; + this can be optimized shortly.""") + c.drawText(t) + + #first the outline + c.saveState() + t = c.beginText(inch, 3.0 * inch) + t.setFont('Helvetica-BoldOblique',108) + t.setTextRenderMode(5) #stroke and add to path + t.textLine('Python!') + t.setTextRenderMode(0) + c.drawText(t) #this will make a clipping mask + + #now some small stuff which wil be drawn into the current clip mask + t = c.beginText(inch, 4 * inch) + t.setFont('Times-Roman',6) + t.textLines((('spam ' * 40) + '\n') * 15) + c.drawText(t) + + #now reset canvas to get rid of the clipping mask + c.restoreState() + + c.showPage() + + +######################################################################### +# +# Page 7 - images +# +######################################################################### + framePage(c, "Images") + c.setFont('Times-Roman', 12) + t = c.beginText(inch, 10 * inch) + if not haveImages: + c.drawString(inch, 11*inch, + "Python or Java Imaging Library not found! Below you see rectangles instead of images.") + + t.textLines("""PDFgen uses the Python Imaging Library (or, under Jython, java.awt.image and javax.imageio) + to process a very wide variety of image formats. + This page shows image capabilities. If I've done things right, the bitmap should have + its bottom left corner aligned with the crosshairs. + There are two methods for drawing images. The recommended use is to call drawImage. + This produces the smallest PDFs and the fastest generation times as each image's binary data is + only embedded once in the file. Also you can use advanced features like transparency masks. + You can also use drawInlineImage, which puts images in the page stream directly. + This is slightly faster for Acrobat to render or for very small images, but wastes + space if you use images more than once.""") + + c.drawText(t) + + if haveImages: + gif = os.path.join(os.path.dirname(unittest.__file__),'pythonpowered.gif') + c.drawInlineImage(gif,2*inch, 7*inch) + else: + c.rect(2*inch, 7*inch, 110, 44) + + c.line(1.5*inch, 7*inch, 4*inch, 7*inch) + c.line(2*inch, 6.5*inch, 2*inch, 8*inch) + c.drawString(4.5 * inch, 7.25*inch, 'inline image drawn at natural size') + + if haveImages: + c.drawInlineImage(gif,2*inch, 5*inch, inch, inch) + else: + c.rect(2*inch, 5*inch, inch, inch) + + c.line(1.5*inch, 5*inch, 4*inch, 5*inch) + c.line(2*inch, 4.5*inch, 2*inch, 6*inch) + c.drawString(4.5 * inch, 5.25*inch, 'inline image distorted to fit box') + + c.drawString(1.5 * inch, 4*inch, 'Image XObjects can be defined once in the file and drawn many times.') + c.drawString(1.5 * inch, 3.75*inch, 'This results in faster generation and much smaller files.') + + for i in range(5): + if haveImages: + (w, h) = c.drawImage(gif, (1.5 + i)*inch, 3*inch) + else: + c.rect((1.5 + i)*inch, 3*inch, 110, 44) + + myMask = [254,255,222,223,0,1] + c.drawString(1.5 * inch, 2.5*inch, "The optional 'mask' parameter lets you define transparent colors. We used a color picker") + c.drawString(1.5 * inch, 2.3*inch, "to determine that the yellow in the image above is RGB=(225,223,0). We then define a mask") + c.drawString(1.5 * inch, 2.1*inch, "spanning these RGB values: %s. The background vanishes!!" % myMask) + c.drawString(2.5*inch, 1.2*inch, 'This would normally be obscured') + if haveImages: + c.drawImage(gif, 1*inch, 1.2*inch, w, h, mask=myMask) + c.drawImage(gif, 3*inch, 1.2*inch, w, h, mask='auto') + else: + c.rect(1*inch, 1.2*inch, w, h) + c.rect(3*inch, 1.2*inch, w, h) + + c.showPage() + + if haveImages: + import shutil + c.drawString(1*inch, 10.25*inch, 'This jpeg is actually a gif') + jpg = outputfile('_i_am_actually_a_gif.jpg') + shutil.copyfile(gif,jpg) + c.drawImage(jpg, 1*inch, 9.25*inch, w, h, mask='auto') + tjpg = os.path.join(os.path.dirname(os.path.dirname(gif)),'docs','images','lj8100.jpg') + if os.path.isfile(tjpg): + c.drawString(4*inch, 10.25*inch, 'This gif is actually a jpeg') + tgif = outputfile(os.path.basename('_i_am_actually_a_jpeg.gif')) + shutil.copyfile(tjpg,tgif) + c.drawImage(tgif, 4*inch, 9.25*inch, w, h, mask='auto') + c.showPage() + + +######################################################################### +# +# Page 8 - Forms and simple links +# +######################################################################### + framePage(c, "Forms and Links") + c.setFont('Times-Roman', 12) + t = c.beginText(inch, 10 * inch) + t.textLines("""Forms are sequences of text or graphics operations + which are stored only once in a PDF file and used as many times + as desired. The blue logo bar to the left is an example of a form + in this document. See the function framePageForm in this demo script + for an example of how to use canvas.beginForm(name, ...) ... canvas.endForm(). + + Documents can also contain cross references where (for example) a rectangle + on a page may be bound to a position on another page. If the user clicks + on the rectangle the PDF viewer moves to the bound position on the other + page. There are many other types of annotations and links supported by PDF. + + For example, there is a bookmark to each page in this document and below + is a browsable index that jumps to those pages. In addition we show two + URL hyperlinks; for these, you specify a rectangle but must draw the contents + or any surrounding rectangle yourself. + """) + c.drawText(t) + + nentries = len(titlelist) + xmargin = 3*inch + xmax = 7*inch + ystart = 6.54*inch + ydelta = 0.4*inch + for i in range(nentries): + yposition = ystart - i*ydelta + title = titlelist[i] + c.drawString(xmargin, yposition, title) + c.linkAbsolute(title, title, (xmargin-ydelta/4, yposition-ydelta/4, xmax, yposition+ydelta/2)) + + # test URLs + r1 = (inch, 3*inch, 5*inch, 3.25*inch) # this is x1,y1,x2,y2 + c.linkURL('http://www.reportlab.com/', r1, thickness=1, color=colors.green) + c.drawString(inch+3, 3*inch+6, 'Hyperlink to www.reportlab.com, with green border') + + r1 = (inch, 2.5*inch, 5*inch, 2.75*inch) # this is x1,y1,x2,y2 + c.linkURL('mailto:reportlab-users@egroups.com', r1) #, border=0) + c.drawString(inch+3, 2.5*inch+6, 'mailto: hyperlink, without border') + + r1 = (inch, 2*inch, 5*inch, 2.25*inch) # this is x1,y1,x2,y2 + c.linkURL('http://www.reportlab.com/', r1, + thickness=2, + dashArray=[2,4], + color=colors.magenta) + c.drawString(inch+3, 2*inch+6, 'Hyperlink with custom border style') + + xpdf = outputfile('test_hello.pdf').replace('\\','/') + link = 'Hard link to %s, with red border' % xpdf + r1 = (inch, 1.5*inch, inch+2*3+c.stringWidth(link,c._fontname, c._fontsize), 1.75*inch) # this is x1,y1,x2,y2 + c.linkURL(xpdf, r1, thickness=1, color=colors.red, kind='GoToR') + c.drawString(inch+3, 1.5*inch+6, link ) + + ### now do stuff for the outline + #for x in outlinenametree: print x + #stop + #apply(c.setOutlineNames0, tuple(outlinenametree)) + return c + + +def run(filename): + c = makeDocument(filename) + c.save() + c = makeDocument(filename) + import os + f = os.path.splitext(filename) + f = open('%sm%s' % (f[0],f[1]),'wb') + f.write(c.getpdfdata()) + f.close() + + + +def pageShapes(c): + """Demonstrates the basic lines and shapes""" + + c.showPage() + framePage(c, "Basic line and shape routines""") + c.setTextOrigin(inch, 10 * inch) + c.setFont('Times-Roman', 12) + c.textLines("""pdfgen provides some basic routines for drawing straight and curved lines, + and also for solid shapes.""") + + y = 9 * inch + d = DocBlock() + d.comment1 = 'Lesson one' + d.code = "canvas.textOut('hello, world')" + print d.code + + d.comment2 = 'Lesson two' + + d.draw(c, inch, 9 * inch) + + +class PdfgenTestCase(unittest.TestCase): + "Make documents with lots of Pdfgen features" + + def test0(self): + "Make a PDFgen document with most graphics features" + run(outputfile('test_pdfgen_general.pdf')) + +def makeSuite(): + return makeSuiteForClasses(PdfgenTestCase) + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_pdfgen_links.py b/bin/reportlab/test/test_pdfgen_links.py new file mode 100644 index 00000000000..7f3b87acd26 --- /dev/null +++ b/bin/reportlab/test/test_pdfgen_links.py @@ -0,0 +1,187 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#this test and associates functionality kinds donated by Ian Sparks. +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/test/test_pdfgen_links.py +""" +Tests for internal links and destinations +""" + +# +# Fit tests +# +# Modification History +# ==================== +# +# 11-Mar-2003 Ian Sparks +# * Initial version. +# +# +from reportlab.pdfgen import canvas +from reportlab.lib.units import inch +from reportlab.lib.pagesizes import letter +from reportlab.lib import colors + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +def markPage(c,height=letter[1],width=letter[0]): + height = height / inch + width = width / inch + for y in range(int(height)): + for x in range(int(width)): + c.drawString(x*inch,y*inch,"x=%d y=%d" % (x,y) ) + c.line(x*inch,0,x*inch,height*inch) + c.line(0,y*inch,width*inch,y*inch) + +fn = outputfile("test_pdfgen_links.pdf") + + +class LinkTestCase(unittest.TestCase): + "Test classes." + def test1(self): + + c = canvas.Canvas(fn,pagesize=letter) + #Page 1 + c.setFont("Courier", 10) + markPage(c) + + c.bookmarkPage("P1") + c.addOutlineEntry("Page 1","P1") + + #Note : XYZ Left is ignored because at this zoom the whole page fits the screen + c.bookmarkPage("P1_XYZ",fit="XYZ",top=7*inch,left=3*inch,zoom=0.5) + c.addOutlineEntry("Page 1 XYZ #1 (top=7,left=3,zoom=0.5)","P1_XYZ",level=1) + + c.bookmarkPage("P1_XYZ2",fit="XYZ",top=7*inch,left=3*inch,zoom=5) + c.addOutlineEntry("Page 1 XYZ #2 (top=7,left=3,zoom=5)","P1_XYZ2",level=1) + + c.bookmarkPage("P1_FIT",fit="Fit") + c.addOutlineEntry("Page 1 Fit","P1_FIT",level=1) + + c.bookmarkPage("P1_FITH",fit="FitH",top=2*inch) + c.addOutlineEntry("Page 1 FitH (top = 2 inch)","P1_FITH",level=1) + + c.bookmarkPage("P1_FITV",fit="FitV",left=3*inch) + c.addOutlineEntry("Page 1 FitV (left = 3 inch)","P1_FITV",level=1) + + c.bookmarkPage("P1_FITR",fit="FitR",left=1*inch,bottom=2*inch,right=5*inch,top=6*inch) + c.addOutlineEntry("Page 1 FitR (left=1,bottom=2,right=5,top=6)","P1_FITR",level=1) + + c.bookmarkPage("P1_FORWARD") + c.addOutlineEntry("Forward References","P1_FORWARD",level=2) + c.addOutlineEntry("Page 3 XYZ (top=7,left=3,zoom=0)","P3_XYZ",level=3) + + + #Create link to FitR on page 3 + c.saveState() + c.setFont("Courier", 14) + c.setFillColor(colors.blue) + c.drawString(inch+20,inch+20,"Click to jump to the meaning of life") + c.linkAbsolute("","MOL",(inch+10,inch+10,6*inch,2*inch)) + c.restoreState() + + #Create linkAbsolute to page 2 + c.saveState() + c.setFont("Courier", 14) + c.setFillColor(colors.green) + c.drawString(4*inch,4*inch,"Jump to 2.5 inch position on page 2") + c.linkAbsolute("","HYPER_1",(3.75*inch,3.75*inch,8.25*inch,4.25*inch)) + c.restoreState() + + + c.showPage() + + #Page 2 + c.setFont("Helvetica", 10) + markPage(c) + + c.bookmarkPage("P2") + c.addOutlineEntry("Page 2","P2") + + #Note : This time left will be at 3*inch because the zoom makes the page to big to fit + c.bookmarkPage("P2_XYZ",fit="XYZ",top=7*inch,left=3*inch,zoom=2) + c.addOutlineEntry("Page 2 XYZ (top=7,left=3,zoom=2.0)","P2_XYZ",level=1) + + c.bookmarkPage("P2_FIT",fit="Fit") + c.addOutlineEntry("Page 2 Fit","P2_FIT",level=1) + + c.bookmarkPage("P2_FITH",fit="FitH",top=2*inch) + c.addOutlineEntry("Page 2 FitH (top = 2 inch)","P2_FITH",level=1) + + c.bookmarkPage("P2_FITV",fit="FitV",left=10*inch) + c.addOutlineEntry("Page 2 FitV (left = 10 inch)","P2_FITV",level=1) + + c.bookmarkPage("P2_FITR",fit="FitR",left=1*inch,bottom=2*inch,right=5*inch,top=6*inch) + c.addOutlineEntry("Page 2 FitR (left=1,bottom=2,right=5,top=6)","P2_FITR",level=1) + + c.bookmarkPage("P2_FORWARD") + c.addOutlineEntry("Forward References","P2_FORWARD",level=2) + c.addOutlineEntry("Page 3 XYZ (top=7,left=3,zoom=0)","P3_XYZ",level=3) + c.bookmarkPage("P2_BACKWARD") + c.addOutlineEntry("Backward References","P2_BACKWARD",level=2) + c.addOutlineEntry("Page 1 Fit","P1_FIT",level=3) + c.addOutlineEntry("Page 1 FitR (left=1,bottom=2,right=5,top=6)","P1_FITR",level=3) + + #Horizontal absolute test from page 1. Note that because of the page size used on page 3 all this will do + #is put the view centered on the bookmark. If you want to see it "up close and personal" change page3 to be + #the same page size as the other pages. + c.saveState() + c.setFont("Courier", 14) + c.setFillColor(colors.green) + c.drawString(2.5*inch,2.5*inch,"This line is hyperlinked from page 1") + # c.bookmarkHorizontalAbsolute("HYPER_1",3*inch) #slightly higher than the text otherwise text is of screen above. + c.bookmarkPage("HYPER_1",fit="XYZ",top=2.5*inch,bottom=2*inch) + c.restoreState() + + # + + c.showPage() + + #Page 3 + c.setFont("Times-Roman", 10) + #Turn the page on its size and make it 2* the normal "width" in order to have something to test FitV against. + c.setPageSize((2*letter[1],letter[0])) + markPage(c,height=letter[0],width=2*letter[1]) + + c.bookmarkPage("P3") + c.addOutlineEntry("Page 3 (Double-wide landscape page)","P3") + + #Note : XYZ with no zoom (set it to something first + c.bookmarkPage("P3_XYZ",fit="XYZ",top=7*inch,left=3*inch,zoom=0) + c.addOutlineEntry("Page 3 XYZ (top=7,left=3,zoom=0)","P3_XYZ",level=1) + + #FitV works here because the page is so wide it can"t all fit on the page + c.bookmarkPage("P3_FITV",fit="FitV",left=10*inch) + c.addOutlineEntry("Page 3 FitV (left = 10 inch)","P3_FITV",level=1) + + + c.bookmarkPage("P3_BACKWARD") + c.addOutlineEntry("Backward References","P3_BACKWARD",level=2) + c.addOutlineEntry("Page 1 XYZ #1 (top=7,left=3,zoom=0.5)","P1_XYZ",level=3) + c.addOutlineEntry("Page 1 XYZ #2 (top=7,left=3,zoom=5)","P1_XYZ2",level=3) + c.addOutlineEntry("Page 2 FitV (left = 10 inch)","P2_FITV",level=3) + + #Add link from page 1 + c.saveState() + c.setFont("Courier", 40) + c.setFillColor(colors.green) + c.drawString(5*inch,6*inch,"42") + c.bookmarkPage("MOL",fit="FitR",left=4*inch,top=7*inch,bottom=4*inch,right=6*inch) + + + + + c.showOutline() + c.save() + + + +def makeSuite(): + return makeSuiteForClasses(LinkTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + print "wrote", fn + printLocation() diff --git a/bin/reportlab/test/test_pdfgen_pagemodes.py b/bin/reportlab/test/test_pdfgen_pagemodes.py new file mode 100644 index 00000000000..fdf6ab465b0 --- /dev/null +++ b/bin/reportlab/test/test_pdfgen_pagemodes.py @@ -0,0 +1,70 @@ +#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/test/test_pdfgen_pagemodes.py +# full screen test + +"""Tests for PDF page modes support in reportlab.pdfgen. +""" + + +import os + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfgen.canvas import Canvas + + +def fileDoesExist(path): + "Check if a file does exist." + return os.path.exists(path) + + +class PdfPageModeTestCase(unittest.TestCase): + "Testing different page modes for opening a file in Acrobat Reader." + + baseFileName = 'test_pagemodes_' + + def _doTest(self, filename, mode, desc): + "A generic method called by all test real methods." + + filename = outputfile(self.baseFileName + filename) + c = Canvas(filename) + + # Handle different modes. + if mode == 'FullScreen': + c.showFullScreen0() + elif mode == 'Outline': + c.bookmarkPage('page1') + c.addOutlineEntry('Token Outline Entry', 'page1') + c.showOutline() + elif mode == 'UseNone': + pass + + c.setFont('Helvetica', 20) + c.drawString(100, 700, desc) + c.save() + + assert fileDoesExist(filename) + + + def test0(self): + "This should open in full screen mode." + self._doTest('FullScreen.pdf', 'FullScreen', self.test0.__doc__) + + def test1(self): + "This should open with outline visible." + self._doTest('Outline.pdf', 'Outline', self.test1.__doc__) + + def test2(self): + "This should open in the user's default mode." + self._doTest('UseNone.pdf', 'UseNone', self.test2.__doc__) + +def makeSuite(): + return makeSuiteForClasses(PdfPageModeTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_pdfgen_pycanvas.py b/bin/reportlab/test/test_pdfgen_pycanvas.py new file mode 100644 index 00000000000..d7a5c6eee5c --- /dev/null +++ b/bin/reportlab/test/test_pdfgen_pycanvas.py @@ -0,0 +1,790 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +__version__=''' $Id: test_pdfgen_pycanvas.py 2619 2005-06-24 14:49:15Z rgbecker $ ''' +__doc__='testscript for reportlab.pdfgen' +#tests and documents new low-level canvas and the pycanvas module to output Python source code. + +import string, os + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfgen import pycanvas # gmcm 2000/10/13, pdfgen now a package +from reportlab.lib.units import inch, cm +from reportlab.lib import colors +from reportlab.lib.utils import haveImages, _RL_DIR, rl_isfile + +################################################################# +# +# first some drawing utilities +# +# +################################################################ + +BASEFONT = ('Times-Roman', 10) +def framePageForm(c): + c.beginForm("frame") + c.saveState() + # forms can't do non-constant operations + #canvas.setFont('Times-BoldItalic',20) + #canvas.drawString(inch, 10.5 * inch, title) + + #c.setFont('Times-Roman',10) + #c.drawCentredString(4.135 * inch, 0.75 * inch, + # 'Page %d' % c.getPageNumber()) + + #draw a border + c.setFillColor(colors.ReportLabBlue) + c.rect(0.3*inch, inch, 0.5*inch, 10*inch, fill=1) + from reportlab.lib import corp + c.translate(0.8*inch, 9.6*inch) + c.rotate(90) + logo = corp.ReportLabLogo(width=1.3*inch, height=0.5*inch, powered_by=1) + c.setFillColorRGB(1,1,1) + c.setStrokeColorRGB(1,1,1) + logo.draw(c) + #c.setStrokeColorRGB(1,0,0) + #c.setLineWidth(5) + #c.line(0.8 * inch, inch, 0.8 * inch, 10.75 * inch) + #reset carefully afterwards + #canvas.setLineWidth(1) + #canvas.setStrokeColorRGB(0,0,0)\ + c.restoreState() + c.endForm() + +titlelist = [] +closeit = 0 + + +def framePage(canvas, title): + global closeit + titlelist.append(title) + #canvas._inPage0() # do we need this at all? would be good to eliminate it + canvas.saveState() + canvas.setFont('Times-BoldItalic',20) + + canvas.drawString(inch, 10.5 * inch, title) + canvas.bookmarkHorizontalAbsolute(title, 10.8*inch) + #newsection(title) + canvas.addOutlineEntry(title+" section", title, level=0, closed=closeit) + closeit = not closeit # close every other one + canvas.setFont('Times-Roman',10) + canvas.drawCentredString(4.135 * inch, 0.75 * inch, + 'Page %d' % canvas.getPageNumber()) + canvas.restoreState() + canvas.doForm("frame") + + +def makesubsection(canvas, title, horizontal): + canvas.bookmarkHorizontalAbsolute(title, horizontal) + #newsubsection(title) + canvas.addOutlineEntry(title+" subsection", title, level=1) + + +# outline helpers +#outlinenametree = [] +#def newsection(name): +# outlinenametree.append(name) + + +#def newsubsection(name): +# from types import TupleType +# thissection = outlinenametree[-1] +# if type(thissection) is not TupleType: +# subsectionlist = [] +# thissection = outlinenametree[-1] = (thissection, subsectionlist) +# else: +# (sectionname, subsectionlist) = thissection +# subsectionlist.append(name) + + +class DocBlock: + """A DocBlock has a chunk of commentary and a chunk of code. + It prints the code and commentary, then executes the code, + which is presumed to draw in a region reserved for it. + """ + def __init__(self): + self.comment1 = "A doc block" + self.code = "canvas.setTextOrigin(cm, cm)\ncanvas.textOut('Hello World')" + self.comment2 = "That was a doc block" + self.drawHeight = 0 + + def _getHeight(self): + "splits into lines" + self.comment1lines = string.split(self.comment1, '\n') + self.codelines = string.split(self.code, '\n') + self.comment2lines = string.split(self.comment2, '\n') + textheight = (len(self.comment1lines) + + len(self.code) + + len(self.comment2lines) + + 18) + return max(textheight, self.drawHeight) + + def draw(self, canvas, x, y): + #specifies top left corner + canvas.saveState() + height = self._getHeight() + canvas.rect(x, y-height, 6*inch, height) + #first draw the text + canvas.setTextOrigin(x + 3 * inch, y - 12) + canvas.setFont('Times-Roman',10) + canvas.textLines(self.comment1) + drawCode(canvas, self.code) + canvas.textLines(self.comment2) + + #now a box for the drawing, slightly within rect + canvas.rect(x + 9, y - height + 9, 198, height - 18) + #boundary: + self.namespace = {'canvas':canvas,'cm': cm,'inch':inch} + canvas.translate(x+9, y - height + 9) + codeObj = compile(self.code, '','exec') + exec codeObj in self.namespace + + canvas.restoreState() + + +def drawAxes(canvas, label): + """draws a couple of little rulers showing the coords - + uses points as units so you get an imperial ruler + one inch on each side""" + #y axis + canvas.line(0,0,0,72) + for y in range(9): + tenths = (y+1) * 7.2 + canvas.line(-6,tenths,0,tenths) + canvas.line(-6, 66, 0, 72) #arrow... + canvas.line(6, 66, 0, 72) #arrow... + + canvas.line(0,0,72,0) + for x in range(9): + tenths = (x+1) * 7.2 + canvas.line(tenths,-6,tenths, 0) + canvas.line(66, -6, 72, 0) #arrow... + canvas.line(66, +6, 72, 0) #arrow... + + canvas.drawString(18, 30, label) + + +def drawCrossHairs(canvas, x, y): + """just a marker for checking text metrics - blue for fun""" + + canvas.saveState() + canvas.setStrokeColorRGB(0,1,0) + canvas.line(x-6,y,x+6,y) + canvas.line(x,y-6,x,y+6) + canvas.restoreState() + + +def drawCode(canvas, code): + """Draws a block of text at current point, indented and in Courier""" + canvas.addLiteral('36 0 Td') + canvas.setFillColor(colors.blue) + canvas.setFont('Courier',10) + + t = canvas.beginText() + t.textLines(code) + c.drawText(t) + + canvas.setFillColor(colors.black) + canvas.addLiteral('-36 0 Td') + canvas.setFont('Times-Roman',10) + + +def makeDocument(filename, pageCallBack=None): + #the extra arg is a hack added later, so other + #tests can get hold of the canvas just before it is + #saved + + c = pycanvas.Canvas(filename) + c.setPageCompression(0) + c.setPageCallBack(pageCallBack) + framePageForm(c) # define the frame form + c.showOutline() + + framePage(c, 'PDFgen graphics API test script') + makesubsection(c, "PDFgen and PIDDLE", 10*inch) + + t = c.beginText(inch, 10*inch) + t.setFont('Times-Roman', 10) + drawCrossHairs(c, t.getX(),t.getY()) + t.textLines(""" +The ReportLab library permits you to create PDF documents directly from +your Python code. The "pdfgen" subpackage is the lowest level exposed +to the user and lets you directly position test and graphics on the +page, with access to almost the full range of PDF features. + The API is intended to closely mirror the PDF / Postscript imaging +model. There is an almost one to one correspondence between commands +and PDF operators. However, where PDF provides several ways to do a job, +we have generally only picked one. + The test script attempts to use all of the methods exposed by the Canvas +class, defined in reportlab/pdfgen/canvas.py + First, let's look at text output. There are some basic commands +to draw strings: +- canvas.setFont(fontname, fontsize [, leading]) +- canvas.drawString(x, y, text) +- canvas.drawRightString(x, y, text) +- canvas.drawCentredString(x, y, text) + +The coordinates are in points starting at the bottom left corner of the +page. When setting a font, the leading (i.e. inter-line spacing) +defaults to 1.2 * fontsize if the fontsize is not provided. + +For more sophisticated operations, you can create a Text Object, defined +in reportlab/pdfgen/testobject.py. Text objects produce tighter PDF, run +faster and have many methods for precise control of spacing and position. +Basic usage goes as follows: +- tx = canvas.beginText(x, y) +- tx.textOut('Hello') # this moves the cursor to the right +- tx.textLine('Hello again') # prints a line and moves down +- y = tx.getY() # getX, getY and getCursor track position +- canvas.drawText(tx) # all gets drawn at the end + +The green crosshairs below test whether the text cursor is working +properly. They should appear at the bottom left of each relevant +substring. +""") + + t.setFillColorRGB(1,0,0) + t.setTextOrigin(inch, 4*inch) + drawCrossHairs(c, t.getX(),t.getY()) + t.textOut('textOut moves across:') + drawCrossHairs(c, t.getX(),t.getY()) + t.textOut('textOut moves across:') + drawCrossHairs(c, t.getX(),t.getY()) + t.textOut('textOut moves across:') + drawCrossHairs(c, t.getX(),t.getY()) + t.textLine('') + drawCrossHairs(c, t.getX(),t.getY()) + t.textLine('textLine moves down') + drawCrossHairs(c, t.getX(),t.getY()) + t.textLine('textLine moves down') + drawCrossHairs(c, t.getX(),t.getY()) + t.textLine('textLine moves down') + drawCrossHairs(c, t.getX(),t.getY()) + + t.setTextOrigin(4*inch,3.25*inch) + drawCrossHairs(c, t.getX(),t.getY()) + t.textLines('This is a multi-line\nstring with embedded newlines\ndrawn with textLines().\n') + drawCrossHairs(c, t.getX(),t.getY()) + t.textLines(['This is a list of strings', + 'drawn with textLines().']) + c.drawText(t) + + t = c.beginText(2*inch,2*inch) + t.setFont('Times-Roman',10) + drawCrossHairs(c, t.getX(),t.getY()) + t.textOut('Small text.') + drawCrossHairs(c, t.getX(),t.getY()) + t.setFont('Courier',14) + t.textOut('Bigger fixed width text.') + drawCrossHairs(c, t.getX(),t.getY()) + t.setFont('Times-Roman',10) + t.textOut('Small text again.') + drawCrossHairs(c, t.getX(),t.getY()) + c.drawText(t) + + #mark the cursor where it stopped + c.showPage() + + + ############################################################## + # + # page 2 - line styles + # + ############################################################### + + #page 2 - lines and styles + framePage(c, 'Line Drawing Styles') + + + + # three line ends, lines drawn the hard way + #firt make some vertical end markers + c.setDash(4,4) + c.setLineWidth(0) + c.line(inch,9.2*inch,inch, 7.8*inch) + c.line(3*inch,9.2*inch,3*inch, 7.8*inch) + c.setDash() #clears it + + c.setLineWidth(5) + c.setLineCap(0) + p = c.beginPath() + p.moveTo(inch, 9*inch) + p.lineTo(3*inch, 9*inch) + c.drawPath(p) + c.drawString(4*inch, 9*inch, 'the default - butt caps project half a width') + makesubsection(c, "caps and joins", 8.5*inch) + + c.setLineCap(1) + p = c.beginPath() + p.moveTo(inch, 8.5*inch) + p.lineTo(3*inch, 8.5*inch) + c.drawPath(p) + c.drawString(4*inch, 8.5*inch, 'round caps') + + c.setLineCap(2) + p = c.beginPath() + p.moveTo(inch, 8*inch) + p.lineTo(3*inch, 8*inch) + c.drawPath(p) + c.drawString(4*inch, 8*inch, 'square caps') + + c.setLineCap(0) + + # three line joins + c.setLineJoin(0) + p = c.beginPath() + p.moveTo(inch, 7*inch) + p.lineTo(2*inch, 7*inch) + p.lineTo(inch, 6.7*inch) + c.drawPath(p) + c.drawString(4*inch, 6.8*inch, 'Default - mitered join') + + c.setLineJoin(1) + p = c.beginPath() + p.moveTo(inch, 6.5*inch) + p.lineTo(2*inch, 6.5*inch) + p.lineTo(inch, 6.2*inch) + c.drawPath(p) + c.drawString(4*inch, 6.3*inch, 'round join') + + c.setLineJoin(2) + p = c.beginPath() + p.moveTo(inch, 6*inch) + p.lineTo(2*inch, 6*inch) + p.lineTo(inch, 5.7*inch) + c.drawPath(p) + c.drawString(4*inch, 5.8*inch, 'bevel join') + + c.setDash(6,6) + p = c.beginPath() + p.moveTo(inch, 5*inch) + p.lineTo(3*inch, 5*inch) + c.drawPath(p) + c.drawString(4*inch, 5*inch, 'dash 6 points on, 6 off- setDash(6,6) setLineCap(0)') + makesubsection(c, "dash patterns", 5*inch) + + c.setLineCap(1) + p = c.beginPath() + p.moveTo(inch, 4.5*inch) + p.lineTo(3*inch, 4.5*inch) + c.drawPath(p) + c.drawString(4*inch, 4.5*inch, 'dash 6 points on, 6 off- setDash(6,6) setLineCap(1)') + + c.setLineCap(0) + c.setDash([1,2,3,4,5,6],0) + p = c.beginPath() + p.moveTo(inch, 4.0*inch) + p.lineTo(3*inch, 4.0*inch) + c.drawPath(p) + c.drawString(4*inch, 4*inch, 'dash growing - setDash([1,2,3,4,5,6],0) setLineCap(0)') + + c.setLineCap(1) + c.setLineJoin(1) + c.setDash(32,12) + p = c.beginPath() + p.moveTo(inch, 3.0*inch) + p.lineTo(2.5*inch, 3.0*inch) + p.lineTo(inch, 2*inch) + c.drawPath(p) + c.drawString(4*inch, 3*inch, 'dash pattern, join and cap style interacting - ') + c.drawString(4*inch, 3*inch - 12, 'round join & miter results in sausages') + + c.showPage() + + +############################################################## +# +# higher level shapes +# +############################################################### + framePage(c, 'Shape Drawing Routines') + + t = c.beginText(inch, 10*inch) + t.textLines(""" +Rather than making your own paths, you have access to a range of shape routines. +These are built in pdfgen out of lines and bezier curves, but use the most compact +set of operators possible. We can add any new ones that are of general use at no +cost to performance.""") + t.textLine() + + #line demo + makesubsection(c, "lines", 10*inch) + c.line(inch, 8*inch, 3*inch, 8*inch) + t.setTextOrigin(4*inch, 8*inch) + t.textLine('canvas.line(x1, y1, x2, y2)') + + #bezier demo - show control points + makesubsection(c, "bezier curves", 7.5*inch) + (x1, y1, x2, y2, x3, y3, x4, y4) = ( + inch, 6.5*inch, + 1.2*inch, 7.5 * inch, + 3*inch, 7.5 * inch, + 3.5*inch, 6.75 * inch + ) + c.bezier(x1, y1, x2, y2, x3, y3, x4, y4) + c.setDash(3,3) + c.line(x1,y1,x2,y2) + c.line(x3,y3,x4,y4) + c.setDash() + t.setTextOrigin(4*inch, 7 * inch) + t.textLine('canvas.bezier(x1, y1, x2, y2, x3, y3, x4, y4)') + + #rectangle + makesubsection(c, "rectangles", 7*inch) + c.rect(inch, 5.25 * inch, 2 * inch, 0.75 * inch) + t.setTextOrigin(4*inch, 5.5 * inch) + t.textLine('canvas.rect(x, y, width, height) - x,y is lower left') + + #wedge + makesubsection(c, "wedges", 5*inch) + c.wedge(inch, 5*inch, 3*inch, 4*inch, 0, 315) + t.setTextOrigin(4*inch, 4.5 * inch) + t.textLine('canvas.wedge(x1, y1, x2, y2, startDeg, extentDeg)') + t.textLine('Note that this is an elliptical arc, not just circular!') + + #wedge the other way + c.wedge(inch, 4*inch, 3*inch, 3*inch, 0, -45) + t.setTextOrigin(4*inch, 3.5 * inch) + t.textLine('Use a negative extent to go clockwise') + + #circle + makesubsection(c, "circles", 3.5*inch) + c.circle(1.5*inch, 2*inch, 0.5 * inch) + c.circle(3*inch, 2*inch, 0.5 * inch) + t.setTextOrigin(4*inch, 2 * inch) + t.textLine('canvas.circle(x, y, radius)') + c.drawText(t) + + c.showPage() + +############################################################## +# +# Page 4 - fonts +# +############################################################### + framePage(c, "Font Control") + + c.drawString(inch, 10*inch, 'Listing available fonts...') + + y = 9.5*inch + for fontname in c.getAvailableFonts(): + c.setFont(fontname,24) + c.drawString(inch, y, 'This should be %s' % fontname) + y = y - 28 + makesubsection(c, "fonts and colors", 4*inch) + + c.setFont('Times-Roman', 12) + t = c.beginText(inch, 4*inch) + t.textLines("""Now we'll look at the color functions and how they interact + with the text. In theory, a word is just a shape; so setFillColorRGB() + determines most of what you see. If you specify other text rendering + modes, an outline color could be defined by setStrokeColorRGB() too""") + c.drawText(t) + + t = c.beginText(inch, 2.75 * inch) + t.setFont('Times-Bold',36) + t.setFillColor(colors.green) #green + t.textLine('Green fill, no stroke') + + #t.setStrokeColorRGB(1,0,0) #ou can do this in a text object, or the canvas. + t.setStrokeColor(colors.red) #ou can do this in a text object, or the canvas. + t.setTextRenderMode(2) # fill and stroke + t.textLine('Green fill, red stroke - yuk!') + + t.setTextRenderMode(0) # back to default - fill only + t.setFillColorRGB(0,0,0) #back to default + t.setStrokeColorRGB(0,0,0) #ditto + c.drawText(t) + c.showPage() + +######################################################################### +# +# Page 5 - coord transforms +# +######################################################################### + framePage(c, "Coordinate Transforms") + c.setFont('Times-Roman', 12) + t = c.beginText(inch, 10 * inch) + t.textLines("""This shows coordinate transformations. We draw a set of axes, + moving down the page and transforming space before each one. + You can use saveState() and restoreState() to unroll transformations. + Note that functions which track the text cursor give the cursor position + in the current coordinate system; so if you set up a 6 inch high frame + 2 inches down the page to draw text in, and move the origin to its top + left, you should stop writing text after six inches and not eight.""") + c.drawText(t) + + drawAxes(c, "0. at origin") + c.addLiteral('%about to translate space') + c.translate(2*inch, 7 * inch) + drawAxes(c, '1. translate near top of page') + + c.saveState() + c.translate(1*inch, -2 * inch) + drawAxes(c, '2. down 2 inches, across 1') + c.restoreState() + + c.saveState() + c.translate(0, -3 * inch) + c.scale(2, -1) + drawAxes(c, '3. down 3 from top, scale (2, -1)') + c.restoreState() + + c.saveState() + c.translate(0, -5 * inch) + c.rotate(-30) + drawAxes(c, "4. down 5, rotate 30' anticlockwise") + c.restoreState() + + c.saveState() + c.translate(3 * inch, -5 * inch) + c.skew(0,30) + drawAxes(c, "5. down 5, 3 across, skew beta 30") + c.restoreState() + + c.showPage() + +######################################################################### +# +# Page 6 - clipping +# +######################################################################### + framePage(c, "Clipping") + c.setFont('Times-Roman', 12) + t = c.beginText(inch, 10 * inch) + t.textLines("""This shows clipping at work. We draw a chequerboard of rectangles + into a path object, and clip it. This then forms a mask which limits the region of + the page on which one can draw. This paragraph was drawn after setting the clipping + path, and so you should only see part of the text.""") + c.drawText(t) + + c.saveState() + #c.setFillColorRGB(0,0,1) + p = c.beginPath() + #make a chesboard effect, 1 cm squares + for i in range(14): + x0 = (3 + i) * cm + for j in range(7): + y0 = (16 + j) * cm + p.rect(x0, y0, 0.85*cm, 0.85*cm) + c.addLiteral('%Begin clip path') + c.clipPath(p) + c.addLiteral('%End clip path') + t = c.beginText(3 * cm, 22.5 * cm) + t.textLines("""This shows clipping at work. We draw a chequerboard of rectangles + into a path object, and clip it. This then forms a mask which limits the region of + the page on which one can draw. This paragraph was drawn after setting the clipping + path, and so you should only see part of the text. + This shows clipping at work. We draw a chequerboard of rectangles + into a path object, and clip it. This then forms a mask which limits the region of + the page on which one can draw. This paragraph was drawn after setting the clipping + path, and so you should only see part of the text. + This shows clipping at work. We draw a chequerboard of rectangles + into a path object, and clip it. This then forms a mask which limits the region of + the page on which one can draw. This paragraph was drawn after setting the clipping + path, and so you should only see part of the text.""") + c.drawText(t) + + c.restoreState() + + t = c.beginText(inch, 5 * inch) + t.textLines("""You can also use text as an outline for clipping with the text render mode. + The API is not particularly clean on this and one has to follow the right sequence; + this can be optimized shortly.""") + c.drawText(t) + + #first the outline + c.saveState() + t = c.beginText(inch, 3.0 * inch) + t.setFont('Helvetica-BoldOblique',108) + t.setTextRenderMode(5) #stroke and add to path + t.textLine('Python!') + t.setTextRenderMode(0) + c.drawText(t) #this will make a clipping mask + + #now some small stuff which wil be drawn into the current clip mask + t = c.beginText(inch, 4 * inch) + t.setFont('Times-Roman',6) + t.textLines((('spam ' * 40) + '\n') * 15) + c.drawText(t) + + #now reset canvas to get rid of the clipping mask + c.restoreState() + + c.showPage() + + +######################################################################### +# +# Page 7 - images +# +######################################################################### + framePage(c, "Images") + c.setFont('Times-Roman', 12) + t = c.beginText(inch, 10 * inch) + if not haveImages: + c.drawString(inch, 11*inch, + "Python Imaging Library not found! Below you see rectangles instead of images.") + + t.textLines("""PDFgen uses the Python Imaging Library to process a very wide variety of image formats. + This page shows image capabilities. If I've done things right, the bitmap should have + its bottom left corner aligned with the crosshairs. + There are two methods for drawing images. The recommended use is to call drawImage. + This produces the smallest PDFs and the fastest generation times as each image's binary data is + only embedded once in the file. Also you can use advanced features like transparency masks. + You can also use drawInlineImage, which puts images in the page stream directly. + This is slightly faster for Acrobat to render or for very small images, but wastes + space if you use images more than once.""") + + c.drawText(t) + + gif = os.path.join(_RL_DIR,'test','pythonpowered.gif') + if haveImages and rl_isfile(gif): + c.drawInlineImage(gif,2*inch, 7*inch) + else: + c.rect(2*inch, 7*inch, 110, 44) + + c.line(1.5*inch, 7*inch, 4*inch, 7*inch) + c.line(2*inch, 6.5*inch, 2*inch, 8*inch) + c.drawString(4.5 * inch, 7.25*inch, 'inline image drawn at natural size') + + if haveImages and rl_isfile(gif): + c.drawInlineImage(gif,2*inch, 5*inch, inch, inch) + else: + c.rect(2*inch, 5*inch, inch, inch) + + c.line(1.5*inch, 5*inch, 4*inch, 5*inch) + c.line(2*inch, 4.5*inch, 2*inch, 6*inch) + c.drawString(4.5 * inch, 5.25*inch, 'inline image distorted to fit box') + + c.drawString(1.5 * inch, 4*inch, 'Image XObjects can be defined once in the file and drawn many times.') + c.drawString(1.5 * inch, 3.75*inch, 'This results in faster generation and much smaller files.') + + for i in range(5): + if haveImages: + (w, h) = c.drawImage(gif, (1.5 + i)*inch, 3*inch) + else: + c.rect((1.5 + i)*inch, 3*inch, 110, 44) + + myMask = [254,255,222,223,0,1] + c.drawString(1.5 * inch, 2.5*inch, "The optional 'mask' parameter lets you define transparent colors. We used a color picker") + c.drawString(1.5 * inch, 2.3*inch, "to determine that the yellow in the image above is RGB=(225,223,0). We then define a mask") + c.drawString(1.5 * inch, 2.1*inch, "spanning these RGB values: %s. The background vanishes!!" % myMask) + c.drawString(2.5*inch, 1.2*inch, 'This would normally be obscured') + if haveImages: + c.drawImage(gif, 3*inch, 1.2*inch, w, h, mask=myMask) + else: + c.rect(3*inch, 1.2*inch, 110, 44) + + c.showPage() + + +######################################################################### +# +# Page 8 - Forms and simple links +# +######################################################################### + framePage(c, "Forms and Links") + c.setFont('Times-Roman', 12) + t = c.beginText(inch, 10 * inch) + t.textLines("""Forms are sequences of text or graphics operations + which are stored only once in a PDF file and used as many times + as desired. The blue logo bar to the left is an example of a form + in this document. See the function framePageForm in this demo script + for an example of how to use canvas.beginForm(name, ...) ... canvas.endForm(). + + Documents can also contain cross references where (for example) a rectangle + on a page may be bound to a position on another page. If the user clicks + on the rectangle the PDF viewer moves to the bound position on the other + page. There are many other types of annotations and links supported by PDF. + + For example, there is a bookmark to each page in this document and below + is a browsable index that jumps to those pages. In addition we show two + URL hyperlinks; for these, you specify a rectangle but must draw the contents + or any surrounding rectangle yourself. + """) + c.drawText(t) + + nentries = len(titlelist) + xmargin = 3*inch + xmax = 7*inch + ystart = 6.54*inch + ydelta = 0.4*inch + for i in range(nentries): + yposition = ystart - i*ydelta + title = titlelist[i] + c.drawString(xmargin, yposition, title) + c.linkAbsolute(title, title, (xmargin-ydelta/4, yposition-ydelta/4, xmax, yposition+ydelta/2)) + + + # test URLs + r1 = (inch, 3*inch, 5*inch, 3.25*inch) # this is x1,y1,x2,y2 + c.linkURL('http://www.reportlab.com/', r1, thickness=1, color=colors.green) + c.drawString(inch+3, 3*inch+6, 'Hyperlink to www.reportlab.com, with green border') + + r1 = (inch, 2.5*inch, 5*inch, 2.75*inch) # this is x1,y1,x2,y2 + c.linkURL('mailto:reportlab-users@egroups.com', r1) #, border=0) + c.drawString(inch+3, 2.5*inch+6, 'mailto: hyperlink, without border') + + r1 = (inch, 2*inch, 5*inch, 2.25*inch) # this is x1,y1,x2,y2 + c.linkURL('http://www.reportlab.com/', r1, + thickness=2, + dashArray=[2,4], + color=colors.magenta) + c.drawString(inch+3, 2*inch+6, 'Hyperlink with custom border style') + + ### now do stuff for the outline + #for x in outlinenametree: print x + #stop + #apply(c.setOutlineNames0, tuple(outlinenametree)) + return c + + +def run(filename): + c = makeDocument(filename) + c.save() + source = str(c) + open(outputfile("test_pdfgen_pycanvas_out.txt"),"w").write(source) + import reportlab.rl_config + if reportlab.rl_config.verbose: + print source + + +def pageShapes(c): + """Demonstrates the basic lines and shapes""" + + c.showPage() + framePage(c, "Basic line and shape routines""") + c.setTextOrigin(inch, 10 * inch) + c.setFont('Times-Roman', 12) + c.textLines("""pdfgen provides some basic routines for drawing straight and curved lines, + and also for solid shapes.""") + + y = 9 * inch + d = DocBlock() + d.comment1 = 'Lesson one' + d.code = "canvas.textOut('hello, world')" + print d.code + + d.comment2 = 'Lesson two' + + d.draw(c, inch, 9 * inch) + + +class PdfgenTestCase(unittest.TestCase): + "Make documents with lots of Pdfgen features" + + def test0(self): + "Make a PDFgen document with most graphics features" + run(outputfile('test_pdfgen_pycanvas.pdf')) + +def makeSuite(): + return makeSuiteForClasses(PdfgenTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_platypus_breaking.py b/bin/reportlab/test/test_platypus_breaking.py new file mode 100644 index 00000000000..7db78203a1e --- /dev/null +++ b/bin/reportlab/test/test_platypus_breaking.py @@ -0,0 +1,114 @@ +#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/test/test_platypus_breaking.py +"""Tests pageBreakBefore, frameBreakBefore, keepWithNext... +""" + +import sys, os, time +from string import split, strip, join, whitespace +from operator import truth +from types import StringType, ListType + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.platypus.flowables import Flowable +from reportlab.lib import colors +from reportlab.lib.units import cm +from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.frames import Frame +from reportlab.lib.randomtext import randomText, PYTHON +from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate +from reportlab.platypus.paragraph import * + + +def myMainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + canvas.setFont('Times-Roman', 12) + pageNumber = canvas.getPageNumber() + canvas.drawString(10*cm, cm, str(pageNumber)) + canvas.restoreState() + + +class MyDocTemplate(BaseDocTemplate): + _invalidInitArgs = ('pageTemplates',) + + def __init__(self, filename, **kw): + frame1 = Frame(2.5*cm, 15.5*cm, 6*cm, 10*cm, id='F1') + frame2 = Frame(11.5*cm, 15.5*cm, 6*cm, 10*cm, id='F2') + frame3 = Frame(2.5*cm, 2.5*cm, 6*cm, 10*cm, id='F3') + frame4 = Frame(11.5*cm, 2.5*cm, 6*cm, 10*cm, id='F4') + self.allowSplitting = 0 + self.showBoundary = 1 + apply(BaseDocTemplate.__init__, (self, filename), kw) + template = PageTemplate('normal', [frame1, frame2, frame3, frame4], myMainPageFrame) + self.addPageTemplates(template) + + +def _test0(self): + "This makes one long multi-page paragraph." + + # Build story. + story = [] + + styleSheet = getSampleStyleSheet() + h1 = styleSheet['Heading1'] + h1.pageBreakBefore = 1 + h1.keepWithNext = 1 + + h2 = styleSheet['Heading2'] + h2.frameBreakBefore = 1 + h2.keepWithNext = 1 + + h3 = styleSheet['Heading3'] + h3.backColor = colors.cyan + h3.keepWithNext = 1 + + bt = styleSheet['BodyText'] + + story.append(Paragraph(""" + Subsequent pages test pageBreakBefore, frameBreakBefore and + keepTogether attributes. Generated at %s. The number in brackets + at the end of each paragraph is its position in the story. (%d)""" % ( + time.ctime(time.time()), len(story)), bt)) + + for i in range(10): + story.append(Paragraph('Heading 1 always starts a new page (%d)' % len(story), h1)) + for j in range(3): + story.append(Paragraph('Heading1 paragraphs should always' + 'have a page break before. Heading 2 on the other hand' + 'should always have a FRAME break before (%d)' % len(story), bt)) + story.append(Paragraph('Heading 2 always starts a new frame (%d)' % len(story), h2)) + story.append(Paragraph('Heading1 paragraphs should always' + 'have a page break before. Heading 2 on the other hand' + 'should always have a FRAME break before (%d)' % len(story), bt)) + for j in range(3): + story.append(Paragraph(randomText(theme=PYTHON, sentences=2)+' (%d)' % len(story), bt)) + story.append(Paragraph('I should never be at the bottom of a frame (%d)' % len(story), h3)) + story.append(Paragraph(randomText(theme=PYTHON, sentences=1)+' (%d)' % len(story), bt)) + + doc = MyDocTemplate(outputfile('test_platypus_breaking.pdf')) + doc.multiBuild(story) + + +class BreakingTestCase(unittest.TestCase): + "Test multi-page splitting of paragraphs (eyeball-test)." + def test0(self): + _test0(self) + + +def makeSuite(): + return makeSuiteForClasses(BreakingTestCase) + + +#noruntests +if __name__ == "__main__": #NORUNTESTS + if 'debug' in sys.argv: + _test0(None) + else: + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_platypus_general.py b/bin/reportlab/test/test_platypus_general.py new file mode 100644 index 00000000000..1bf87c94579 --- /dev/null +++ b/bin/reportlab/test/test_platypus_general.py @@ -0,0 +1,581 @@ +#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/test/test_platypus_general.py +__version__=''' $Id: test_platypus_general.py 2619 2005-06-24 14:49:15Z rgbecker $ ''' + +#tests and documents Page Layout API +__doc__="""This is not obvious so here's a brief explanation. This module is both +the test script and user guide for layout. Each page has two frames on it: +one for commentary, and one for demonstration objects which may be drawn in +various esoteric ways. The two functions getCommentary() and getExamples() +return the 'story' for each. The run() function gets the stories, then +builds a special "document model" in which the frames are added to each page +and drawn into. +""" + +import string, copy, sys, os + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfgen import canvas +from reportlab import platypus +from reportlab.platypus import BaseDocTemplate, PageTemplate, Flowable, FrameBreak +from reportlab.platypus import Paragraph, Preformatted +from reportlab.lib.units import inch, cm +from reportlab.lib.styles import PropertySet, getSampleStyleSheet, ParagraphStyle +from reportlab.lib import colors +from reportlab.rl_config import defaultPageSize +from reportlab.lib.utils import haveImages, _RL_DIR, rl_isfile, open_for_read +if haveImages: + _GIF = os.path.join(_RL_DIR,'test','pythonpowered.gif') + if not rl_isfile(_GIF): _GIF = None +else: + _GIF = None +_JPG = os.path.join(_RL_DIR,'docs','images','lj8100.jpg') +if not rl_isfile(_JPG): _JPG = None + +def getFurl(fn): + furl = fn.replace(os.sep,'/') + if sys.platform=='win32' and furl[1]==':': furl = furl[0]+'|'+furl[2:] + if furl[0]!='/': furl = '/'+furl + return 'file://'+furl + +PAGE_HEIGHT = defaultPageSize[1] + +################################################################# +# +# first some drawing utilities +# +# +################################################################ + +BASEFONT = ('Times-Roman', 10) + +def framePage(canvas,doc): + #canvas.drawImage("snkanim.gif", 36, 36) + canvas.saveState() + canvas.setStrokeColorRGB(1,0,0) + canvas.setLineWidth(5) + canvas.line(66,72,66,PAGE_HEIGHT-72) + + canvas.setFont('Times-Italic',12) + canvas.drawRightString(523, PAGE_HEIGHT - 56, "Platypus User Guide and Test Script") + + canvas.setFont('Times-Roman',12) + canvas.drawString(4 * inch, 0.75 * inch, + "Page %d" % canvas.getPageNumber()) + canvas.restoreState() + +def getParagraphs(textBlock): + """Within the script, it is useful to whack out a page in triple + quotes containing separate paragraphs. This breaks one into its + constituent paragraphs, using blank lines as the delimiter.""" + lines = string.split(textBlock, '\n') + paras = [] + currentPara = [] + for line in lines: + if len(string.strip(line)) == 0: + #blank, add it + if currentPara <> []: + paras.append(string.join(currentPara, '\n')) + currentPara = [] + else: + currentPara.append(line) + #...and the last one + if currentPara <> []: + paras.append(string.join(currentPara, '\n')) + + return paras + +def getCommentary(): + """Returns the story for the commentary - all the paragraphs.""" + + styleSheet = getSampleStyleSheet() + + story = [] + story.append(Paragraph(""" + PLATYPUS User Guide and Test Script + """, styleSheet['Heading1'])) + + + spam = """ + Welcome to PLATYPUS! + + Platypus stands for "Page Layout and Typography Using Scripts". It is a high + level page layout library which lets you programmatically create complex + documents with a minimum of effort. + + This document is both the user guide & the output of the test script. + In other words, a script used platypus to create the document you are now + reading, and the fact that you are reading it proves that it works. Or + rather, that it worked for this script anyway. It is a first release! + + Platypus is built 'on top of' PDFgen, the Python library for creating PDF + documents. To learn about PDFgen, read the document testpdfgen.pdf. + + """ + + for text in getParagraphs(spam): + story.append(Paragraph(text, styleSheet['BodyText'])) + + story.append(Paragraph(""" + What concepts does PLATYPUS deal with? + """, styleSheet['Heading2'])) + story.append(Paragraph(""" + The central concepts in PLATYPUS are Flowable Objects, Frames, Flow + Management, Styles and Style Sheets, Paragraphs and Tables. This is + best explained in contrast to PDFgen, the layer underneath PLATYPUS. + PDFgen is a graphics library, and has primitive commans to draw lines + and strings. There is nothing in it to manage the flow of text down + the page. PLATYPUS works at the conceptual level fo a desktop publishing + package; you can write programs which deal intelligently with graphic + objects and fit them onto the page. + """, styleSheet['BodyText'])) + + story.append(Paragraph(""" + How is this document organized? + """, styleSheet['Heading2'])) + + story.append(Paragraph(""" + Since this is a test script, we'll just note how it is organized. + the top of each page contains commentary. The bottom half contains + example drawings and graphic elements to whicht he commentary will + relate. Down below, you can see the outline of a text frame, and + various bits and pieces within it. We'll explain how they work + on the next page. + """, styleSheet['BodyText'])) + + story.append(FrameBreak()) + ####################################################################### + # Commentary Page 2 + ####################################################################### + + story.append(Paragraph(""" + Flowable Objects + """, styleSheet['Heading2'])) + spam = """ + The first and most fundamental concept is that of a 'Flowable Object'. + In PDFgen, you draw stuff by calling methods of the canvas to set up + the colors, fonts and line styles, and draw the graphics primitives. + If you set the pen color to blue, everything you draw after will be + blue until you change it again. And you have to handle all of the X-Y + coordinates yourself. + + A 'Flowable object' is exactly what it says. It knows how to draw itself + on the canvas, and the way it does so is totally independent of what + you drew before or after. Furthermore, it draws itself at the location + on the page you specify. + + The most fundamental Flowable Objects in most documents are likely to be + paragraphs, tables, diagrams/charts and images - but there is no + restriction. You can write your own easily, and I hope that people + will start to contribute them. PINGO users - we provide a "PINGO flowable" object to let + you insert platform-independent graphics into the flow of a document. + + When you write a flowable object, you inherit from Flowable and + must implement two methods. object.wrap(availWidth, availHeight) will be called by other parts of + the system, and tells you how much space you have. You should return + how much space you are going to use. For a fixed-size object, this + is trivial, but it is critical - PLATYPUS needs to figure out if things + will fit on the page before drawing them. For other objects such as paragraphs, + the height is obviously determined by the available width. + + + The second method is object.draw(). Here, you do whatever you want. + The Flowable base class sets things up so that you have an origin of + (0,0) for your drawing, and everything will fit nicely if you got the + height and width right. It also saves and restores the graphics state + around your calls, so you don;t have to reset all the properties you + changed. + + Programs which actually draw a Flowable don't + call draw() this directly - they call object.drawOn(canvas, x, y). + So you can write code in your own coordinate system, and things + can be drawn anywhere on the page (possibly even scaled or rotated). + """ + for text in getParagraphs(spam): + story.append(Paragraph(text, styleSheet['BodyText'])) + + story.append(FrameBreak()) + ####################################################################### + # Commentary Page 3 + ####################################################################### + + story.append(Paragraph(""" + Available Flowable Objects + """, styleSheet['Heading2'])) + + story.append(Paragraph(""" + Platypus comes with a basic set of flowable objects. Here we list their + class names and tell you what they do: + """, styleSheet['BodyText'])) + #we can use the bullet feature to do a definition list + story.append(Paragraph(""" + This is a contrived object to give an example of a Flowable - + just a fixed-size box with an X through it and a centred string.""", + styleSheet['Definition'], + bulletText='XBox ' #hack - spot the extra space after + )) + + story.append(Paragraph(""" + This is the basic unit of a document. Paragraphs can be finely + tuned and offer a host of properties through their associated + ParagraphStyle.""", + styleSheet['Definition'], + bulletText='Paragraph ' #hack - spot the extra space after + )) + + story.append(Paragraph(""" + This is used for printing code and other preformatted text. + There is no wrapping, and line breaks are taken where they occur. + Many paragraph style properties do not apply. You may supply + an optional 'dedent' parameter to trim a number of characters + off the front of each line.""", + styleSheet['Definition'], + bulletText='Preformatted ' #hack - spot the extra space after + )) + story.append(Paragraph(""" + This is a straight wrapper around an external image file. By default + the image will be drawn at a scale of one pixel equals one point, and + centred in the frame. You may supply an optional width and height.""", + styleSheet['Definition'], + bulletText='Image ' #hack - spot the extra space after + )) + + story.append(Paragraph(""" + This is a table drawing class; it is intended to be simpler + than a full HTML table model yet be able to draw attractive output, + and behave intelligently when the numbers of rows and columns vary. + Still need to add the cell properties (shading, alignment, font etc.)""", + styleSheet['Definition'], + bulletText='Table ' #hack - spot the extra space after + )) + + story.append(Paragraph(""" + This is a 'null object' which merely takes up space on the page. + Use it when you want some extra padding betweene elements.""", + styleSheet['Definition'], + bulletText='Spacer ' #hack - spot the extra space after + )) + + story.append(Paragraph(""" + A FrameBreak causes the document to call its handle_frameEnd method.""", + styleSheet['Definition'], + bulletText='FrameBreak ' #hack - spot the extra space after + )) + + story.append(Paragraph(""" + This is in progress, but a macro is basically a chunk of Python code to + be evaluated when it is drawn. It could do lots of neat things.""", + styleSheet['Definition'], + bulletText='Macro ' #hack - spot the extra space after + )) + + story.append(FrameBreak()) + + story.append(Paragraph( + "The next example uses a custom font", + styleSheet['Italic'])) + def code(txt,story=story,styleSheet=styleSheet): + story.append(Preformatted(txt,styleSheet['Code'])) + code('''import reportlab.rl_config + reportlab.rl_config.warnOnMissingFontGlyphs = 0 + + from reportlab.pdfbase import pdfmetrics + fontDir = os.path.join(os.path.dirname(reportlab.__file__),'fonts') + face = pdfmetrics.EmbeddedType1Face(os.path.join(fontDir,'LeERC___.AFM'), + os.path.join(fontDir,'LeERC___.PFB')) + faceName = face.name # should be 'LettErrorRobot-Chrome' + pdfmetrics.registerTypeFace(face) + font = pdfmetrics.Font(faceName, faceName, 'WinAnsiEncoding') + pdfmetrics.registerFont(font) + + + # put it inside a paragraph. + story.append(Paragraph( + """This is an ordinary paragraph, which happens to contain + text in an embedded font: + LettErrorRobot-Chrome. + Now for the real challenge...""", styleSheet['Normal'])) + + + styRobot = ParagraphStyle('Robot', styleSheet['Normal']) + styRobot.fontSize = 16 + styRobot.leading = 20 + styRobot.fontName = 'LettErrorRobot-Chrome' + + story.append(Paragraph( + "This whole paragraph is 16-point Letterror-Robot-Chrome.", + styRobot))''') + + story.append(FrameBreak()) + if _GIF: + story.append(Paragraph("""We can use images via the file name""", styleSheet['BodyText'])) + code(''' story.append(platypus.Image('%s'))'''%_GIF) + code(''' story.append(platypus.Image('%s'.encode('utf8')))''' % _GIF) + story.append(Paragraph("""They can also be used with a file URI or from an open python file!""", styleSheet['BodyText'])) + code(''' story.append(platypus.Image('%s'))'''% getFurl(_GIF)) + code(''' story.append(platypus.Image(open_for_read('%s','b')))''' % _GIF) + story.append(FrameBreak()) + story.append(Paragraph("""Images can even be obtained from the internet.""", styleSheet['BodyText'])) + code(''' img = platypus.Image('http://www.reportlab.com/rsrc/encryption.gif') + story.append(img)''') + story.append(FrameBreak()) + + if _JPG: + story.append(Paragraph("""JPEGs are a native PDF image format. They should be available even if PIL cannot be used.""", styleSheet['BodyText'])) + story.append(FrameBreak()) + return story + +def getExamples(): + """Returns all the example flowable objects""" + styleSheet = getSampleStyleSheet() + + story = [] + + #make a style with indents and spacing + sty = ParagraphStyle('obvious', None) + sty.leftIndent = 18 + sty.rightIndent = 18 + sty.firstLineIndent = 18 + sty.spaceBefore = 6 + sty.spaceAfter = 6 + story.append(Paragraph("""Now for some demo stuff - we need some on this page, + even before we explain the concepts fully""", styleSheet['BodyText'])) + p = Paragraph(""" + Platypus is all about fitting objects into frames on the page. You + are looking at a fairly simple Platypus paragraph in Debug mode. + It has some gridlines drawn around it to show the left and right indents, + and the space before and after, all of which are attributes set in + the style sheet. To be specific, this paragraph has left and + right indents of 18 points, a first line indent of 36 points, + and 6 points of space before and after itself. A paragraph + object fills the width of the enclosing frame, as you would expect.""", sty) + + p.debug = 1 #show me the borders + story.append(p) + + story.append(Paragraph("""Same but with justification 1.5 extra leading and green text.""", styleSheet['BodyText'])) + p = Paragraph(""" + Platypus is all about fitting objects into frames on the page. You + are looking at a fairly simple Platypus paragraph in Debug mode. + It has some gridlines drawn around it to show the left and right indents, + and the space before and after, all of which are attributes set in + the style sheet. To be specific, this paragraph has left and + right indents of 18 points, a first line indent of 36 points, + and 6 points of space before and after itself. A paragraph + object fills the width of the enclosing frame, as you would expect.""", sty) + + p.debug = 1 #show me the borders + story.append(p) + + story.append(platypus.XBox(4*inch, 0.75*inch, + 'This is a box with a fixed size')) + + story.append(Paragraph(""" + All of this is being drawn within a text frame which was defined + on the page. This frame is in 'debug' mode so you can see the border, + and also see the margins which it reserves. A frame does not have + to have margins, but they have been set to 6 points each to create + a little space around the contents. + """, styleSheet['BodyText'])) + + story.append(FrameBreak()) + + ####################################################################### + # Examples Page 2 + ####################################################################### + + story.append(Paragraph(""" + Here's the base class for Flowable... + """, styleSheet['Italic'])) + + code = '''class Flowable: + """Abstract base class for things to be drawn. Key concepts: + 1. It knows its size + 2. It draws in its own coordinate system (this requires the + base API to provide a translate() function. + """ + def __init__(self): + self.width = 0 + self.height = 0 + self.wrapped = 0 + + def drawOn(self, canvas, x, y): + "Tell it to draw itself on the canvas. Do not override" + self.canv = canvas + self.canv.saveState() + self.canv.translate(x, y) + + self.draw() #this is the bit you overload + + self.canv.restoreState() + del self.canv + + def wrap(self, availWidth, availHeight): + """This will be called by the enclosing frame before objects + are asked their size, drawn or whatever. It returns the + size actually used.""" + return (self.width, self.height) + ''' + + story.append(Preformatted(code, styleSheet['Code'], dedent=4)) + story.append(FrameBreak()) + ####################################################################### + # Examples Page 3 + ####################################################################### + + story.append(Paragraph( + "Here are some examples of the remaining objects above.", + styleSheet['Italic'])) + + story.append(Paragraph("This is a bullet point", styleSheet['Bullet'], bulletText='O')) + story.append(Paragraph("Another bullet point", styleSheet['Bullet'], bulletText='O')) + + + story.append(Paragraph("""Here is a Table, which takes all kinds of formatting options...""", + styleSheet['Italic'])) + story.append(platypus.Spacer(0, 12)) + + g = platypus.Table( + (('','North','South','East','West'), + ('Quarter 1',100,200,300,400), + ('Quarter 2',100,200,300,400), + ('Total',200,400,600,800)), + (72,36,36,36,36), + (24, 16,16,18) + ) + style = platypus.TableStyle([('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('ALIGN', (0,0), (-1,0), 'CENTRE'), + ('GRID', (0,0), (-1,-1), 0.25, colors.black), + ('LINEBELOW', (0,0), (-1,0), 2, colors.black), + ('LINEBELOW',(1,-1), (-1, -1), 2, (0.5, 0.5, 0.5)), + ('TEXTCOLOR', (0,1), (0,-1), colors.black), + ('BACKGROUND', (0,0), (-1,0), (0,0.7,0.7)) + ]) + g.setStyle(style) + story.append(g) + story.append(FrameBreak()) + + ####################################################################### + # Examples Page 4 - custom fonts + ####################################################################### + # custom font with LettError-Robot font + import reportlab.rl_config + reportlab.rl_config.warnOnMissingFontGlyphs = 0 + + from reportlab.pdfbase import pdfmetrics + fontDir = os.path.join(os.path.dirname(reportlab.__file__),'fonts') + face = pdfmetrics.EmbeddedType1Face(os.path.join(fontDir,'LeERC___.AFM'),os.path.join(fontDir,'LeERC___.PFB')) + faceName = face.name # should be 'LettErrorRobot-Chrome' + pdfmetrics.registerTypeFace(face) + font = pdfmetrics.Font(faceName, faceName, 'WinAnsiEncoding') + pdfmetrics.registerFont(font) + + + # put it inside a paragraph. + story.append(Paragraph( + """This is an ordinary paragraph, which happens to contain + text in an embedded font: + LettErrorRobot-Chrome. + Now for the real challenge...""", styleSheet['Normal'])) + + + styRobot = ParagraphStyle('Robot', styleSheet['Normal']) + styRobot.fontSize = 16 + styRobot.leading = 20 + styRobot.fontName = 'LettErrorRobot-Chrome' + + story.append(Paragraph( + "This whole paragraph is 16-point Letterror-Robot-Chrome.", + styRobot)) + story.append(FrameBreak()) + + if _GIF: + story.append(Paragraph("Here is an Image flowable obtained from a string filename.",styleSheet['Italic'])) + story.append(platypus.Image(_GIF)) + story.append(Paragraph( "Here is an Image flowable obtained from a utf8 filename.", styleSheet['Italic'])) + story.append(platypus.Image(_GIF.encode('utf8'))) + story.append(Paragraph("Here is an Image flowable obtained from a string file url.",styleSheet['Italic'])) + story.append(platypus.Image(getFurl(_GIF))) + story.append(Paragraph("Here is an Image flowable obtained from an open file.",styleSheet['Italic'])) + story.append(platypus.Image(open_for_read(_GIF,'b'))) + story.append(FrameBreak()) + try: + img = platypus.Image('http://www.reportlab.com/rsrc/encryption.gif') + story.append(Paragraph("Here is an Image flowable obtained from a string http url.",styleSheet['Italic'])) + story.append(img) + except: + story.append(Paragraph("The image could not be obtained from a string http url.",styleSheet['Italic'])) + story.append(FrameBreak()) + + if _JPG: + img = platypus.Image(_JPG) + story.append(Paragraph("Here is an JPEG Image flowable obtained from a filename.",styleSheet['Italic'])) + story.append(img) + story.append(Paragraph("Here is an JPEG Image flowable obtained from an open file.",styleSheet['Italic'])) + img = platypus.Image(open_for_read(_JPG,'b')) + story.append(img) + story.append(FrameBreak()) + + + return story + +class AndyTemplate(BaseDocTemplate): + _invalidInitArgs = ('pageTemplates',) + def __init__(self, filename, **kw): + frame1 = platypus.Frame(inch, 5.6*inch, 6*inch, 5.2*inch,id='F1') + frame2 = platypus.Frame(inch, inch, 6*inch, 4.5*inch, showBoundary=1,id='F2') + self.allowSplitting = 0 + apply(BaseDocTemplate.__init__,(self,filename),kw) + self.addPageTemplates(PageTemplate('normal',[frame1,frame2],framePage)) + + def fillFrame(self,flowables): + f = self.frame + while len(flowables)>0 and f is self.frame: + self.handle_flowable(flowables) + + def build(self, flowables1, flowables2): + assert filter(lambda x: not isinstance(x,Flowable), flowables1)==[], "flowables1 argument error" + assert filter(lambda x: not isinstance(x,Flowable), flowables2)==[], "flowables2 argument error" + self._startBuild() + while (len(flowables1) > 0 or len(flowables1) > 0): + self.clean_hanging() + self.fillFrame(flowables1) + self.fillFrame(flowables2) + + self._endBuild() + + +def showProgress(pageNo): + print 'CALLBACK SAYS: page %d' % pageNo + + +def run(): + doc = AndyTemplate(outputfile('test_platypus_general.pdf')) + #doc.setPageCallBack(showProgress) + commentary = getCommentary() + examples = getExamples() + doc.build(commentary,examples) + + +class PlatypusTestCase(unittest.TestCase): + "Make documents with lots of Platypus features" + + def test0(self): + "Make a platypus document" + run() + + +def makeSuite(): + return makeSuiteForClasses(PlatypusTestCase) + + +#noruntests +if __name__ == "__main__": + if '-debug' in sys.argv: + run() + else: + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_platypus_indents.py b/bin/reportlab/test/test_platypus_indents.py new file mode 100644 index 00000000000..ae7d9f44c87 --- /dev/null +++ b/bin/reportlab/test/test_platypus_indents.py @@ -0,0 +1,140 @@ +#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/test/test_platypus_indents.py +"""Tests for context-dependent indentation +""" + +import sys, os, random +from string import split, strip, join, whitespace +from operator import truth +from types import StringType, ListType + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfbase.pdfmetrics import stringWidth +from reportlab.platypus.paraparser import ParaParser +from reportlab.platypus.flowables import Flowable +from reportlab.lib.colors import Color +from reportlab.lib.units import cm +from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY +from reportlab.lib.utils import _className +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.frames import Frame +from reportlab.platypus.doctemplate \ + import PageTemplate, BaseDocTemplate, Indenter, FrameBreak, NextPageTemplate +from reportlab.platypus import tableofcontents +from reportlab.platypus.tableofcontents import TableOfContents +from reportlab.platypus.tables import TableStyle, Table +from reportlab.platypus.paragraph import * +from reportlab.platypus.paragraph import _getFragWords +from reportlab.platypus.flowables import Spacer + + +def myMainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + + canvas.rect(2.5*cm, 2.5*cm, 15*cm, 25*cm) + canvas.setFont('Times-Roman', 12) + pageNumber = canvas.getPageNumber() + canvas.drawString(10*cm, cm, str(pageNumber)) + + canvas.restoreState() + + +class MyDocTemplate(BaseDocTemplate): + _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) + template1 = PageTemplate('normal', [frame1], myMainPageFrame) + + frame2 = Frame(2.5*cm, 16*cm, 15*cm, 10*cm, id='F2', showBoundary=1) + frame3 = Frame(2.5*cm, 2.5*cm, 15*cm, 10*cm, id='F3', showBoundary=1) + + template2 = PageTemplate('updown', [frame2, frame3]) + self.addPageTemplates([template1, template2]) + + +class IndentTestCase(unittest.TestCase): + "Test multi-page splitting of paragraphs (eyeball-test)." + + def test0(self): + "This makes one long multi-page paragraph." + + # Build story. + story = [] + + styleSheet = getSampleStyleSheet() + h1 = styleSheet['Heading1'] + h1.spaceBefore = 18 + bt = styleSheet['BodyText'] + bt.spaceBefore = 6 + + story.append(Paragraph('Test of context-relative indentation',h1)) + + story.append(Spacer(18,18)) + + story.append(Indenter(0,0)) + story.append(Paragraph("This should be indented 0 points at each edge. " + ("spam " * 25),bt)) + story.append(Indenter(0,0)) + + story.append(Indenter(36,0)) + story.append(Paragraph("This should be indented 36 points at the left. " + ("spam " * 25),bt)) + story.append(Indenter(-36,0)) + + story.append(Indenter(0,36)) + story.append(Paragraph("This should be indented 36 points at the right. " + ("spam " * 25),bt)) + story.append(Indenter(0,-36)) + + story.append(Indenter(36,36)) + story.append(Paragraph("This should be indented 36 points at each edge. " + ("spam " * 25),bt)) + story.append(Indenter(36,36)) + story.append(Paragraph("This should be indented a FURTHER 36 points at each edge. " + ("spam " * 25),bt)) + story.append(Indenter(-72,-72)) + + story.append(Paragraph("This should be back to normal at each edge. " + ("spam " * 25),bt)) + + + story.append(Indenter(36,36)) + story.append(Paragraph(("""This should be indented 36 points at the left + and right. It should run over more than one page and the indent should + continue on the next page. """ + (random.randint(0,10) * 'x') + ' ') * 20 ,bt)) + story.append(Indenter(-36,-36)) + + story.append(NextPageTemplate('updown')) + story.append(FrameBreak()) + story.append(Paragraph('Another test of context-relative indentation',h1)) + story.append(NextPageTemplate('normal')) # so NEXT page is different template... + story.append(Paragraph("""This time we see if the indent level is continued across + frames...this page has 2 frames, let's see if it carries top to bottom. Then + onto a totally different template.""",bt)) + + story.append(Indenter(0,0)) + story.append(Paragraph("This should be indented 0 points at each edge. " + ("spam " * 25),bt)) + story.append(Indenter(0,0)) + story.append(Indenter(36,72)) + story.append(Paragraph(("""This should be indented 36 points at the left + and 72 at the right. It should run over more than one frame and one page, and the indent should + continue on the next page. """ + (random.randint(0,10) * 'x') + ' ') * 35 ,bt)) + + story.append(Indenter(-36,-72)) + story.append(Paragraph("This should be back to normal at each edge. " + ("spam " * 25),bt)) + doc = MyDocTemplate(outputfile('test_platypus_indents.pdf')) + doc.multiBuild(story) + + +#noruntests +def makeSuite(): + return makeSuiteForClasses(IndentTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_platypus_leftright.py b/bin/reportlab/test/test_platypus_leftright.py new file mode 100644 index 00000000000..6f9369ec0e6 --- /dev/null +++ b/bin/reportlab/test/test_platypus_leftright.py @@ -0,0 +1,158 @@ +#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/test/test_platypus_breaking.py +"""Tests ability to cycle through multiple page templates +""" + +import sys, os, time +from string import split, strip, join, whitespace +from operator import truth +from types import StringType, ListType + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.platypus.flowables import Flowable +from reportlab.lib import colors +from reportlab.lib.pagesizes import A4 +from reportlab.lib.units import cm +from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.frames import Frame +from reportlab.lib.randomtext import randomText, PYTHON +from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate, NextPageTemplate +from reportlab.platypus.paragraph import * + + +def myMainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + canvas.setFont('Times-Roman', 12) + pageNumber = canvas.getPageNumber() + canvas.drawString(10*cm, cm, str(pageNumber)) + canvas.restoreState() + + + +class LeftPageTemplate(PageTemplate): + def __init__(self): + #allow a bigger margin on the right for the staples + frame = Frame(1.5*cm, 2.5*cm, 16*cm, 25*cm, id='F1') + + PageTemplate.__init__(self, + id='left', + frames=[frame], + pagesize=A4) + def beforeDrawPage(self, canv, doc): + "Decorate the page with an asymetric design" + canv.setFillColor(colors.cyan) + + canv.rect(0.5*cm, 2.5*cm, 1*cm, 25*cm, stroke=1, fill=1) + canv.circle(19*cm, 10*cm, 0.5*cm, stroke=1, fill=1) + canv.circle(19*cm, 20*cm, 0.5*cm, stroke=1, fill=1) + canv.setFillColor(colors.black) + + +class RightPageTemplate(PageTemplate): + def __init__(self): + #allow a bigger margin on the right for the staples + frame = Frame(3.5*cm, 2.5*cm, 16*cm, 25*cm, id='F1') + + PageTemplate.__init__(self, + id='right', + frames=[frame], + pagesize=A4) + def beforeDrawPage(self, canv, doc): + "Decorate the page with an asymetric design" + canv.setFillColor(colors.cyan) + canv.rect(19.5*cm, 2.5*cm, 1*cm, 25*cm, stroke=1, fill=1) + canv.circle(2*cm, 10*cm, 0.5*cm, stroke=1, fill=1) + canv.circle(2*cm, 20*cm, 0.5*cm, stroke=1, fill=1) + canv.setFillColor(colors.black) + + +class MyDocTemplate(BaseDocTemplate): + _invalidInitArgs = ('pageTemplates',) + + def __init__(self, filename, **kw): + apply(BaseDocTemplate.__init__, (self, filename), kw) + self.addPageTemplates( + [ + PageTemplate(id='plain', + frames=[Frame(2.5*cm, 2.5*cm, 16*cm, 25*cm, id='F1')] + ), + LeftPageTemplate(), + RightPageTemplate() + ] + ) + + +class LeftRightTestCase(unittest.TestCase): + "Test multi-page splitting of paragraphs (eyeball-test)." + def testIt(self): + "This makes one long multi-page paragraph." + + # Build story. + story = [] + + styleSheet = getSampleStyleSheet() + h1 = styleSheet['Heading1'] + h1.pageBreakBefore = 1 + h1.keepWithNext = 1 + + h2 = styleSheet['Heading2'] + h2.frameBreakBefore = 1 + h2.keepWithNext = 1 + + h3 = styleSheet['Heading3'] + h3.backColor = colors.cyan + h3.keepWithNext = 1 + + bt = styleSheet['BodyText'] + + story.append(Paragraph(""" + This tests ability to alternate left and right templates. We start on + a plain one. The next page should display a left-side template, + with a big inner margin and staple-like holes on the right.""",style=bt)) + + story.append(NextPageTemplate(['left','right'])) + + + story.append(Paragraph(""" + One can specify a list of templates instead of a single one in + order to sequence through them.""",style=bt)) + for i in range(10): + story.append(Paragraph('Heading 1 always starts a new page (%d)' % len(story), h1)) + for j in range(3): + story.append(Paragraph('Heading1 paragraphs should always' + 'have a page break before. Heading 2 on the other hand' + 'should always have a FRAME break before (%d)' % len(story), bt)) + story.append(Paragraph('Heading 2 always starts a new frame (%d)' % len(story), h2)) + story.append(Paragraph('Heading1 paragraphs should always' + 'have a page break before. Heading 2 on the other hand' + 'should always have a FRAME break before (%d)' % len(story), bt)) + for j in range(3): + story.append(Paragraph(randomText(theme=PYTHON, sentences=2)+' (%d)' % len(story), bt)) + story.append(Paragraph('I should never be at the bottom of a frame (%d)' % len(story), h3)) + story.append(Paragraph(randomText(theme=PYTHON, sentences=1)+' (%d)' % len(story), bt)) + + story.append(NextPageTemplate('plain')) + story.append(Paragraph('Back to plain old page template',h1)) + story.append(Paragraph('Back to plain old formatting', bt)) + + + #doc = MyDocTemplate(outputfile('test_platypus_leftright.pdf')) + doc = MyDocTemplate(outputfile('test_platypus_leftright.pdf')) + doc.multiBuild(story) + + +def makeSuite(): + return makeSuiteForClasses(LeftRightTestCase) + + +#noruntests +if __name__ == "__main__": #NORUNTESTS + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_platypus_paragraphs.py b/bin/reportlab/test/test_platypus_paragraphs.py new file mode 100644 index 00000000000..0720577fc85 --- /dev/null +++ b/bin/reportlab/test/test_platypus_paragraphs.py @@ -0,0 +1,184 @@ +#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/test/test_platypus_paragraphs.py +"""Tests for the reportlab.platypus.paragraphs module. +""" + +import sys, os +from string import split, strip, join, whitespace +from operator import truth +from types import StringType, ListType + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.pdfbase.pdfmetrics import stringWidth +from reportlab.platypus.paraparser import ParaParser +from reportlab.platypus.flowables import Flowable +from reportlab.lib.colors import Color +from reportlab.lib.units import cm +from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY +from reportlab.lib.utils import _className +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.frames import Frame +from reportlab.platypus.doctemplate \ + import PageTemplate, BaseDocTemplate +from reportlab.platypus import tableofcontents +from reportlab.platypus.tableofcontents import TableOfContents +from reportlab.platypus.tables import TableStyle, Table +from reportlab.platypus.paragraph import * +from reportlab.platypus.paragraph import _getFragWords + + +def myMainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + + canvas.rect(2.5*cm, 2.5*cm, 15*cm, 25*cm) + canvas.setFont('Times-Roman', 12) + pageNumber = canvas.getPageNumber() + canvas.drawString(10*cm, cm, str(pageNumber)) + + canvas.restoreState() + + +class MyDocTemplate(BaseDocTemplate): + _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) + template = PageTemplate('normal', [frame1], myMainPageFrame) + self.addPageTemplates(template) + + +class ParagraphCorners(unittest.TestCase): + "some corner cases which should parse" + def check(text,bt = getSampleStyleSheet()['BodyText']): + try: + P = Paragraph(text,st) + except: + raise AssertionError("'%s' should parse"%text) + + def test0(self): + self.check('') + self.check('') + self.check('\t\t\t\n\n\n') + self.check('\t\t\t\n\n\n') + self.check('') + self.check('') + self.check(' ') + self.check('\t\t\n\t\t\t ') + + + +class ParagraphSplitTestCase(unittest.TestCase): + "Test multi-page splitting of paragraphs (eyeball-test)." + + def test0(self): + "This makes one long multi-page paragraph." + + # Build story. + story = [] + styleSheet = getSampleStyleSheet() + bt = styleSheet['BodyText'] + text = '''If you imagine that the box of X's tothe left is +an image, what I want to be able to do is flow a +series of paragraphs around the image +so that once the bottom of the image is reached, then text will flow back to the +left margin. I know that it would be possible to something like this +using tables, but I can't see how to have a generic solution. +There are two examples of this in the demonstration section of the reportlab +site. +If you look at the "minimal" euro python conference brochure, at the end of the +timetable section (page 8), there are adverts for "AdSu" and "O'Reilly". I can +see how the AdSu one might be done generically, but the O'Reilly, unsure... +I guess I'm hoping that I've missed something, and that +it's actually easy to do using platypus. +''' + from reportlab.platypus.flowables import ParagraphAndImage, Image + from reportlab.lib.utils import _RL_DIR + gif = os.path.join(_RL_DIR,'test','pythonpowered.gif') + story.append(ParagraphAndImage(Paragraph(text,bt),Image(gif))) + phrase = 'This should be a paragraph spanning at least three pages. ' + description = ''.join([('%d: '%i)+phrase for i in xrange(250)]) + story.append(ParagraphAndImage(Paragraph(description, bt),Image(gif),side='left')) + + doc = MyDocTemplate(outputfile('test_platypus_paragraphandimage.pdf')) + doc.multiBuild(story) + + def test1(self): + "This makes one long multi-page paragraph." + + # Build story. + story = [] + styleSheet = getSampleStyleSheet() + h3 = styleSheet['Heading3'] + bt = styleSheet['BodyText'] + text = '''If you imagine that the box of X's tothe left is +an image, what I want to be able to do is flow a +series of paragraphs around the image +so that once the bottom of the image is reached, then text will flow back to the +left margin. I know that it would be possible to something like this +using tables, but I can't see how to have a generic solution. +There are two examples of this in the demonstration section of the reportlab +site. +If you look at the "minimal" euro python conference brochure, at the end of the +timetable section (page 8), there are adverts for "AdSu" and "O'Reilly". I can +see how the AdSu one might be done generically, but the O'Reilly, unsure... +I guess I'm hoping that I've missed something, and that +it's actually easy to do using platypus.We can do greek letters mDngG. This should be a +u with a dieresis on top <unichar code=0xfc/>="" and this &#xfc;="ü" and this \\xc3\\xbc="\xc3\xbc". On the other hand this +should be a pound sign &pound;="£" and this an alpha &alpha;="α". You can have links in the page ReportLab. +Use scheme "pdf:" to indicate an external PDF link, "http:", "https:" to indicate an external link eg something to open in +your browser. If an internal link begins with something that looks like a scheme, precede with "document:". +''' + from reportlab.platypus.flowables import ImageAndFlowables, Image + from reportlab.lib.utils import _RL_DIR + gif = os.path.join(_RL_DIR,'test','pythonpowered.gif') + heading = Paragraph('This is a heading',h3) + story.append(ImageAndFlowables(Image(gif),[heading,Paragraph(text,bt)])) + phrase = 'This should be a paragraph spanning at least three pages. ' + description = ''.join([('%d: '%i)+phrase for i in xrange(250)]) + story.append(ImageAndFlowables(Image(gif),[heading,Paragraph(description, bt)],imageSide='left')) + + doc = MyDocTemplate(outputfile('test_platypus_imageandflowables.pdf')) + doc.multiBuild(story) + +class FragmentTestCase(unittest.TestCase): + "Test fragmentation of paragraphs." + + def test0(self): + "Test empty paragraph." + + styleSheet = getSampleStyleSheet() + B = styleSheet['BodyText'] + text = '' + P = Paragraph(text, B) + frags = map(lambda f:f.text, P.frags) + assert frags == [] + + + def test1(self): + "Test simple paragraph." + + styleSheet = getSampleStyleSheet() + B = styleSheet['BodyText'] + text = "XYZ" + P = Paragraph(text, B) + frags = map(lambda f:f.text, P.frags) + assert frags == ['X', 'Y', 'Z'] + + +#noruntests +def makeSuite(): + return makeSuiteForClasses(FragmentTestCase, ParagraphSplitTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_platypus_paraparser.py b/bin/reportlab/test/test_platypus_paraparser.py new file mode 100644 index 00000000000..99cd6f0e4d1 --- /dev/null +++ b/bin/reportlab/test/test_platypus_paraparser.py @@ -0,0 +1,91 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history TBC +#$Header$ +__version__=''' $Id''' +__doc__="""Tests of intra-paragraph parsing behaviour in Platypus.""" + +from types import TupleType, ListType, StringType, UnicodeType +from pprint import pprint as pp + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile +from reportlab.platypus import cleanBlockQuotedText +from reportlab.platypus.paraparser import ParaParser, ParaFrag +from reportlab.lib.colors import black + +class ParaParserTestCase(unittest.TestCase): + """Tests of data structures created by paragraph parser. Esp. ability + to accept unicode and preserve it""" + + def setUp(self): + style=ParaFrag() + style.fontName='Times-Roman' + style.fontSize = 12 + style.textColor = black + style.bulletFontName = black + style.bulletFontName='Times-Roman' + style.bulletFontSize=12 + self.style = style + + def testPlain(self): + txt = "Hello World" + stuff = ParaParser().parse(txt, self.style) + assert type(stuff) is TupleType + assert len(stuff) == 3 + assert stuff[1][0].text == 'Hello World' + + def testBold(self): + txt = "Hello Bold World" + fragList = ParaParser().parse(txt, self.style)[1] + self.assertEquals(map(lambda x:x.text, fragList), ['Hello ','Bold',' World']) + self.assertEquals(fragList[1].fontName, 'Times-Bold') + + def testEntity(self): + "Numeric entities should be unescaped by parser" + txt = "Hello © copyright" + fragList = ParaParser().parse(txt, self.style)[1] + self.assertEquals(map(lambda x:x.text, fragList), ['Hello ','\xc2\xa9',' copyright']) + + def testEscaped(self): + "Escaped high-bit stuff should go straight through" + txt = "Hello \xc2\xa9 copyright" + fragList = ParaParser().parse(txt, self.style)[1] + assert fragList[0].text == txt + + def testPlainUnicode(self): + "See if simple unicode goes through" + txt = u"Hello World" + stuff = ParaParser().parse(txt, self.style) + assert type(stuff) is TupleType + assert len(stuff) == 3 + assert stuff[1][0].text == u'Hello World' + + def testBoldUnicode(self): + txt = u"Hello Bold World" + fragList = ParaParser().parse(txt, self.style)[1] + self.assertEquals(map(lambda x:x.text, fragList), [u'Hello ',u'Bold',u' World']) + self.assertEquals(fragList[1].fontName, 'Times-Bold') + + def testEntityUnicode(self): + "Numeric entities should be unescaped by parser" + txt = u"Hello © copyright" + fragList = ParaParser().parse(txt, self.style)[1] + self.assertEquals(map(lambda x:x.text, fragList), [u'Hello ',u'\xc2\xa9',u' copyright']) + + def testEscapedUnicode(self): + "Escaped high-bit stuff should go straight through" + txt = u"Hello \xa9 copyright" + fragList = ParaParser().parse(txt, self.style)[1] + assert fragList[0].text == txt + + + +def makeSuite(): + return makeSuiteForClasses(ParaParserTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) diff --git a/bin/reportlab/test/test_platypus_pto.py b/bin/reportlab/test/test_platypus_pto.py new file mode 100644 index 00000000000..2d119b2f6a5 --- /dev/null +++ b/bin/reportlab/test/test_platypus_pto.py @@ -0,0 +1,195 @@ +#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/test/test_platypus_breaking.py +"""Tests pageBreakBefore, frameBreakBefore, keepWithNext... +""" + +import sys + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation +from reportlab.platypus.flowables import Flowable, PTOContainer, KeepInFrame +from reportlab.lib.units import cm +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.lib.colors import toColor, black +from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.tables import Table +from reportlab.platypus.frames import Frame +from reportlab.lib.randomtext import randomText +from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate, FrameBreak + +def myMainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + canvas.setFont('Times-Roman', 12) + pageNumber = canvas.getPageNumber() + canvas.drawString(10*cm, cm, str(pageNumber)) + canvas.restoreState() + +def _showDoc(fn,story): + pageTemplate = PageTemplate('normal', [Frame(72, 440, 170, 284, id='F1'), + Frame(326, 440, 170, 284, id='F2'), + Frame(72, 72, 170, 284, id='F3'), + Frame(326, 72, 170, 284, id='F4'), + ], myMainPageFrame) + doc = BaseDocTemplate(outputfile(fn), + pageTemplates = pageTemplate, + showBoundary = 1, + ) + doc.multiBuild(story) + +text2 ='''We have already seen that the natural general principle that will +subsume this case cannot be arbitrary in the requirement that branching +is not tolerated within the dominance scope of a complex symbol. +Notice, incidentally, that the speaker-hearer's linguistic intuition is +to be regarded as the strong generative capacity of the theory. A +consequence of the approach just outlined is that the descriptive power +of the base component does not affect the structure of the levels of +acceptability from fairly high (e.g. (99a)) to virtual gibberish (e.g. +(98d)). By combining adjunctions and certain deformations, a +descriptively adequate grammar cannot be arbitrary in the strong +generative capacity of the theory.''' + +text1=''' +On our assumptions, a descriptively adequate grammar delimits the strong +generative capacity of the theory. For one thing, the fundamental error +of regarding functional notions as categorial is to be regarded as a +corpus of utterance tokens upon which conformity has been defined by the +paired utterance test. A majority of informed linguistic specialists +agree that the appearance of parasitic gaps in domains relatively +inaccessible to ordinary extraction is necessary to impose an +interpretation on the requirement that branching is not tolerated within +the dominance scope of a complex symbol. It may be, then, that the +speaker-hearer's linguistic intuition appears to correlate rather +closely with the ultimate standard that determines the accuracy of any +proposed grammar. Analogously, the notion of level of grammaticalness +may remedy and, at the same time, eliminate a general convention +regarding the forms of the grammar.''' + +text0 = '''To characterize a linguistic level L, +this selectionally introduced contextual +feature delimits the requirement that +branching is not tolerated within the +dominance scope of a complex +symbol. Notice, incidentally, that the +notion of level of grammaticalness +does not affect the structure of the +levels of acceptability from fairly high +(e.g. (99a)) to virtual gibberish (e.g. +(98d)). Suppose, for instance, that a +subset of English sentences interesting +on quite independent grounds appears +to correlate rather closely with an +important distinction in language use. +Presumably, this analysis of a +formative as a pair of sets of features is +not quite equivalent to the system of +base rules exclusive of the lexicon. We +have already seen that the appearance +of parasitic gaps in domains relatively +inaccessible to ordinary extraction +does not readily tolerate the strong +generative capacity of the theory.''' + +def _ptoTestCase(self): + "This makes one long multi-page paragraph." + + # Build story. + story = [] + def fbreak(story=story): + story.append(FrameBreak()) + + styleSheet = getSampleStyleSheet() + H1 = styleSheet['Heading1'] + H1.pageBreakBefore = 0 + H1.keepWithNext = 0 + + bt = styleSheet['BodyText'] + pto = ParagraphStyle('pto',parent=bt) + pto.alignment = TA_RIGHT + pto.fontSize -= 1 + def ColorParagraph(c,text,style): + return Paragraph('%s' % (c,text),style) + + def ptoblob(blurb,content,trailer=None,header=None, story=story, H1=H1): + if type(content) not in (type([]),type(())): content = [content] + story.append(PTOContainer([Paragraph(blurb,H1)]+list(content),trailer,header)) + + t0 = [ColorParagraph('blue','Please turn over', pto )] + h0 = [ColorParagraph('blue','continued from previous page', pto )] + t1 = [ColorParagraph('red','Please turn over(inner)', pto )] + h1 = [ColorParagraph('red','continued from previous page(inner)', pto )] + ptoblob('First Try at a PTO',[Paragraph(text0,bt)],t0,h0) + fbreak() + c1 = Table([('alignment', 'align\012alignment'), + ('bulletColor', 'bulletcolor\012bcolor'), + ('bulletFontName', 'bfont\012bulletfontname'), + ('bulletFontSize', 'bfontsize\012bulletfontsize'), + ('bulletIndent', 'bindent\012bulletindent'), + ('firstLineIndent', 'findent\012firstlineindent'), + ('fontName', 'face\012fontname\012font'), + ('fontSize', 'size\012fontsize'), + ('leading', 'leading'), + ('leftIndent', 'leftindent\012lindent'), + ('rightIndent', 'rightindent\012rindent'), + ('spaceAfter', 'spaceafter\012spacea'), + ('spaceBefore', 'spacebefore\012spaceb'), + ('textColor', 'fg\012textcolor\012color')], + style = [ + ('VALIGN',(0,0),(-1,-1),'TOP'), + ('INNERGRID', (0,0), (-1,-1), 0.25, black), + ('BOX', (0,0), (-1,-1), 0.25, black), + ], + ) + ptoblob('PTO with a table inside',c1,t0,h0) + fbreak() + ptoblob('A long PTO',[Paragraph(text0+' '+text1,bt)],t0,h0) + fbreak() + ptoblob('2 PTO (inner split)',[ColorParagraph('pink',text0,bt),PTOContainer([ColorParagraph(black,'Inner Starts',H1),ColorParagraph('yellow',text2,bt),ColorParagraph('black','Inner Ends',H1)],t1,h1),ColorParagraph('magenta',text1,bt)],t0,h0) + _showDoc('test_platypus_pto.pdf',story) + +def _KeepInFrameTestCase(self,mode,offset=12): + story = [] + def fbreak(story=story): + story.append(FrameBreak()) + styleSheet = getSampleStyleSheet() + H1 = styleSheet['Heading1'] + H1.pageBreakBefore = 0 + H1.keepWithNext = 0 + bt = styleSheet['BodyText'] + story.append(KeepInFrame(170-offset,284-offset,[Paragraph(text0,bt)],mode=mode)) + fbreak() + story.append(KeepInFrame(170-offset,284-offset,[Paragraph(text0,bt),Paragraph(text1,bt)],mode=mode)) + fbreak() + story.append(KeepInFrame(170-offset,284-offset,[Paragraph(text0,bt),Paragraph(text1,bt),Paragraph(text2,bt)],mode=mode)) + _showDoc('test_platypus_KeepInFrame%s.pdf'%mode,story) + +class TestCases(unittest.TestCase): + "Test multi-page splitting of paragraphs (eyeball-test)." + def test0(self): + _ptoTestCase(self) + def test1(self): + _KeepInFrameTestCase(self,mode="shrink") + def test2(self): + _KeepInFrameTestCase(self,mode="overflow") + def test3(self): + _KeepInFrameTestCase(self,mode="truncate") + def test4(self): + from reportlab.platypus.doctemplate import LayoutError + self.assertRaises(LayoutError, _KeepInFrameTestCase,*(self,"error")) + def test5(self): + from reportlab.platypus.doctemplate import LayoutError + self.assertRaises(LayoutError, _KeepInFrameTestCase,*(self,"shrink",0)) + +def makeSuite(): + return makeSuiteForClasses(TestCases) + +#noruntests +if __name__ == "__main__": #NORUNTESTS + if 'debug' in sys.argv: + _KeepInFrameTestCase(None) + else: + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_platypus_tables.py b/bin/reportlab/test/test_platypus_tables.py new file mode 100644 index 00000000000..705d1f9a396 --- /dev/null +++ b/bin/reportlab/test/test_platypus_tables.py @@ -0,0 +1,717 @@ +#!/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/test/test_platypus_tables.py +__version__=''' $Id: test_platypus_tables.py 2848 2006-05-04 23:45:29Z andy $ ''' +__doc__='Test script for reportlab.tables' + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation +from reportlab.platypus import Spacer, SimpleDocTemplate, Table, TableStyle +from reportlab.lib.units import inch, cm +from reportlab.lib import colors + + +def getTable(): + t = Table((('','North','South','East','West'), + ('Quarter 1',100,200,300,400), + ('Quarter 2',100,400,600,800), + ('Total',300,600,900,'1,200')), + (72,36,36,36,36), + (24, 16,16,18) + ) + return t + + +def makeStyles(): + styles = [] + for i in range(5): + styles.append(TableStyle([('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('ALIGN', (0,0), (-1,0), 'CENTRE'), + ('HREF', (0,0), (0,0), 'www.google.com'), + ])) + for style in styles[1:]: + style.add('GRID', (0,0), (-1,-1), 0.25, colors.black) + for style in styles[2:]: + style.add('LINEBELOW', (0,0), (-1,0), 2, colors.black) + for style in styles[3:]: + style.add('LINEABOVE', (0, -1), (-1,-1), 2, colors.black) + styles[-1].add('LINEBELOW',(1,-1), (-1, -1), 2, (0.5, 0.5, 0.5)) + return styles + + +def run(): + doc = SimpleDocTemplate(outputfile('test_platypus_tables.pdf'), pagesize=(8.5*inch, 11*inch), showBoundary=1) + styles = makeStyles() + lst = [] + for style in styles: + t = getTable() + t.setStyle(style) +## print '--------------' +## for rowstyle in t._cellstyles: +## for s in rowstyle: +## print s.alignment + lst.append(t) + lst.append(Spacer(0,12)) + doc.build(lst) + +def old_tables_test(): + from reportlab.lib.units import inch, cm + from reportlab.platypus.flowables import Image, PageBreak, Spacer, XBox + from reportlab.platypus.paragraph import Paragraph + from reportlab.platypus.xpreformatted import XPreformatted + from reportlab.platypus.flowables import Preformatted + from reportlab.platypus.doctemplate import SimpleDocTemplate + from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle + from reportlab.platypus.tables import GRID_STYLE, BOX_STYLE, LABELED_GRID_STYLE, COLORED_GRID_STYLE, LIST_STYLE, LongTable + rowheights = (24, 16, 16, 16, 16) + rowheights2 = (24, 16, 16, 16, 30) + colwidths = (50, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32) + data = ( + ('', 'Jan', 'Feb', 'Mar','Apr','May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), + ('Mugs', 0, 4, 17, 3, 21, 47, 12, 33, 2, -2, 44, 89), + ('T-Shirts', 0, 42, 9, -3, 16, 4, 72, 89, 3, 19, 32, 119), + ('Key Ring', 0,0,0,0,0,0,1,0,0,0,2,13), + ('Hats', 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, 453, '1,344','2,843') + ) + data2 = ( + ('', 'Jan', 'Feb', 'Mar','Apr','May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), + ('Mugs', 0, 4, 17, 3, 21, 47, 12, 33, 2, -2, 44, 89), + ('T-Shirts', 0, 42, 9, -3, 16, 4, 72, 89, 3, 19, 32, 119), + ('Key Ring', 0,0,0,0,0,0,1,0,0,0,2,13), + ('Hats\nLarge', 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, 453, '1,344','2,843') + ) + styleSheet = getSampleStyleSheet() + lst = [] + lst.append(Paragraph("Tables", styleSheet['Heading1'])) + lst.append(Paragraph(__doc__, styleSheet['BodyText'])) + lst.append(Paragraph("The Tables (shown in different styles below) were created using the following code:", styleSheet['BodyText'])) + lst.append(Preformatted(""" + colwidths = (50, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32) + rowheights = (24, 16, 16, 16, 16) + data = ( + ('', 'Jan', 'Feb', 'Mar','Apr','May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), + ('Mugs', 0, 4, 17, 3, 21, 47, 12, 33, 2, -2, 44, 89), + ('T-Shirts', 0, 42, 9, -3, 16, 4, 72, 89, 3, 19, 32, 119), + ('Key Ring', 0,0,0,0,0,0,1,0,0,0,2,13), + ('Hats', 893, 912, '1,212', 643, 789, 159, + 888, '1,298', 832, 453, '1,344','2,843') + ) + t = Table(data, colwidths, rowheights) + """, styleSheet['Code'], dedent=4)) + lst.append(Paragraph(""" + You can then give the Table a TableStyle object to control its format. The first TableStyle used was + created as follows: + """, styleSheet['BodyText'])) + lst.append(Preformatted(""" +GRID_STYLE = TableStyle( + [('GRID', (0,0), (-1,-1), 0.25, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT')] + ) + """, styleSheet['Code'])) + lst.append(Paragraph(""" + TableStyles are created by passing in a list of commands. There are two types of commands - line commands + and cell formatting commands. In all cases, the first three elements of a command are the command name, + the starting cell and the ending cell. + """, styleSheet['BodyText'])) + lst.append(Paragraph(""" + Line commands always follow this with the weight and color of the desired lines. Colors can be names, + or they can be specified as a (R,G,B) tuple, where R, G and B are floats and (0,0,0) is black. The line + command names are: GRID, BOX, OUTLINE, INNERGRID, LINEBELOW, LINEABOVE, LINEBEFORE + and LINEAFTER. BOX and OUTLINE are equivalent, and GRID is the equivalent of applying both BOX and + INNERGRID. + """, styleSheet['BodyText'])) + lst.append(Paragraph(""" + Cell formatting commands are: + """, styleSheet['BodyText'])) + lst.append(Paragraph(""" + FONT - takes fontname, fontsize and (optional) leading. + """, styleSheet['Definition'])) + lst.append(Paragraph(""" + TEXTCOLOR - takes a color name or (R,G,B) tuple. + """, styleSheet['Definition'])) + lst.append(Paragraph(""" + ALIGNMENT (or ALIGN) - takes one of LEFT, RIGHT, CENTRE (or CENTER) or DECIMAL. + """, styleSheet['Definition'])) + lst.append(Paragraph(""" + LEFTPADDING - defaults to 6. + """, styleSheet['Definition'])) + lst.append(Paragraph(""" + RIGHTPADDING - defaults to 6. + """, styleSheet['Definition'])) + lst.append(Paragraph(""" + BOTTOMPADDING - defaults to 3. + """, styleSheet['Definition'])) + lst.append(Paragraph(""" + A tablestyle is applied to a table by calling Table.setStyle(tablestyle). + """, styleSheet['BodyText'])) + t = Table(data, colwidths, rowheights) + t.setStyle(GRID_STYLE) + lst.append(PageBreak()) + lst.append(Paragraph("This is GRID_STYLE\n", styleSheet['BodyText'])) + lst.append(t) + + t = Table(data, colwidths, rowheights) + t.setStyle(BOX_STYLE) + lst.append(Paragraph("This is BOX_STYLE\n", styleSheet['BodyText'])) + lst.append(t) + lst.append(Paragraph(""" + It was created as follows: + """, styleSheet['BodyText'])) + lst.append(Preformatted(""" +BOX_STYLE = TableStyle( + [('BOX', (0,0), (-1,-1), 0.50, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT')] + ) + """, styleSheet['Code'])) + + t = Table(data, colwidths, rowheights) + t.setStyle(LABELED_GRID_STYLE) + lst.append(Paragraph("This is LABELED_GRID_STYLE\n", styleSheet['BodyText'])) + lst.append(t) + t = Table(data2, colwidths, rowheights2) + t.setStyle(LABELED_GRID_STYLE) + lst.append(Paragraph("This is LABELED_GRID_STYLE ILLUSTRATES EXPLICIT LINE SPLITTING WITH NEWLINE (different heights and data)\n", styleSheet['BodyText'])) + lst.append(t) + lst.append(Paragraph(""" + It was created as follows: + """, styleSheet['BodyText'])) + lst.append(Preformatted(""" +LABELED_GRID_STYLE = TableStyle( + [('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), + ('BOX', (0,0), (-1,-1), 2, colors.black), + ('LINEBELOW', (0,0), (-1,0), 2, colors.black), + ('LINEAFTER', (0,0), (0,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT')] + ) + """, styleSheet['Code'])) + lst.append(PageBreak()) + + t = Table(data, colwidths, rowheights) + t.setStyle(COLORED_GRID_STYLE) + lst.append(Paragraph("This is COLORED_GRID_STYLE\n", styleSheet['BodyText'])) + lst.append(t) + lst.append(Paragraph(""" + It was created as follows: + """, styleSheet['BodyText'])) + lst.append(Preformatted(""" +COLORED_GRID_STYLE = TableStyle( + [('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), + ('BOX', (0,0), (-1,-1), 2, colors.red), + ('LINEBELOW', (0,0), (-1,0), 2, colors.black), + ('LINEAFTER', (0,0), (0,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT')] + ) + """, styleSheet['Code'])) + + t = Table(data, colwidths, rowheights) + t.setStyle(LIST_STYLE) + lst.append(Paragraph("This is LIST_STYLE\n", styleSheet['BodyText'])) + lst.append(t) + lst.append(Paragraph(""" + It was created as follows: + """, styleSheet['BodyText'])) + lst.append(Preformatted(""" +LIST_STYLE = TableStyle( + [('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), + ('ALIGN', (1,1), (-1,-1), 'RIGHT')] + ) + """, styleSheet['Code'])) + + t = Table(data, colwidths, rowheights) + ts = TableStyle( + [('LINEABOVE', (0,0), (-1,0), 2, colors.green), + ('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + ('LINEBELOW', (0,-1), (-1,-1), 3, colors.green,'butt'), + ('LINEBELOW', (0,-1), (-1,-1), 1, colors.white,'butt'), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('TEXTCOLOR', (0,1), (0,-1), colors.red), + ('BACKGROUND', (0,0), (-1,0), colors.Color(0,0.7,0.7))] + ) + t.setStyle(ts) + lst.append(Paragraph("This is a custom style\n", styleSheet['BodyText'])) + lst.append(t) + lst.append(Paragraph(""" + It was created as follows: + """, styleSheet['BodyText'])) + lst.append(Preformatted(""" + ts = TableStyle( + [('LINEABOVE', (0,0), (-1,0), 2, colors.green), + ('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + ('LINEBELOW', (0,-1), (-1,-1), 3, colors.green,'butt'), + ('LINEBELOW', (0,-1), (-1,-1), 1, colors.white,'butt'), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('TEXTCOLOR', (0,1), (0,-1), colors.red), + ('BACKGROUND', (0,0), (-1,0), colors.Color(0,0.7,0.7))] + ) + """, styleSheet['Code'])) + data = ( + ('', 'Jan\nCold', 'Feb\n', 'Mar\n','Apr\n','May\n', 'Jun\nHot', 'Jul\n', 'Aug\nThunder', 'Sep\n', 'Oct\n', 'Nov\n', 'Dec\n'), + ('Mugs', 0, 4, 17, 3, 21, 47, 12, 33, 2, -2, 44, 89), + ('T-Shirts', 0, 42, 9, -3, 16, 4, 72, 89, 3, 19, 32, 119), + ('Key Ring', 0,0,0,0,0,0,1,0,0,0,2,13), + ('Hats', 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, 453, '1,344','2,843') + ) + c = list(colwidths) + c[0] = None + c[8] = None + t = Table(data, c, [None]+list(rowheights[1:])) + t.setStyle(LIST_STYLE) + lst.append(Paragraph(""" + This is a LIST_STYLE table with the first rowheight set to None ie automatic. + The top row cells are split at a newline '\\n' character. The first and August + column widths were also set to None. + """, styleSheet['BodyText'])) + lst.append(t) + + lst.append(Paragraph(""" + This demonstrates a number of features useful in financial statements. The first is decimal alignment; + with ALIGN=DECIMAL the numbers align on the points; and the points are aligned based on + the RIGHTPADDING, which is usually 3 points so you should set it higher. The second is multiple lines; + one can specify double or triple lines and control the separation if desired. Finally, the coloured + negative numbers were (we regret to say) done in the style; we don't have a way to conditionally + format numbers based on value yet. + """, styleSheet['BodyText'])) + + + t = Table([[u'Corporate Assets','Amount'], + ['Fixed Assets','1,234,567.89'], + ['Company Vehicle','1,234.8901'], + ['Petty Cash','42'], + [u'Intellectual Property\u00ae','(42,078,231.56)'], + ['Overdraft','(12,345)'], + ['Boardroom Flat Screen','60 inches'], + ['Net Position','Deep Sh*t.Really'] + ], + [144,72]) + + ts = TableStyle( + [#first the top row + ('ALIGN', (1,1), (-1,-1), 'CENTER'), + ('LINEABOVE', (0,0), (-1,0), 1, colors.purple), + ('LINEBELOW', (0,0), (-1,0), 1, colors.purple), + ('FONT', (0,0), (-1,0), 'Times-Bold'), + + #bottom row has a line above, and two lines below + ('LINEABOVE', (0,-1), (-1,-1), 1, colors.purple), #last 2 are count, sep + ('LINEBELOW', (0,-1), (-1,-1), 0.5, colors.purple, 1, None, None, 4,1), + ('LINEBELOW', (0,-1), (-1,-1), 1, colors.red), + ('FONT', (0,-1), (-1,-1), 'Times-Bold'), + + #numbers column + ('ALIGN', (1,1), (-1,-1), 'DECIMAL'), + ('RIGHTPADDING', (1,1), (-1,-1), 36), + ('TEXTCOLOR', (1,4), (1,4), colors.red), + + #red cell + ] + ) + + t.setStyle(ts) + lst.append(t) + lst.append(Spacer(36,36)) + lst.append(Paragraph(""" + The red numbers should be aligned LEFT & BOTTOM, the blue RIGHT & TOP + and the green CENTER & MIDDLE. + """, styleSheet['BodyText'])) + XY = [['X00y', 'X01y', 'X02y', 'X03y', 'X04y'], + ['X10y', 'X11y', 'X12y', 'X13y', 'X14y'], + ['X20y', 'X21y', 'X22y', 'X23y', 'X24y'], + ['X30y', 'X31y', 'X32y', 'X33y', 'X34y']] + t=Table(XY, 5*[0.6*inch], 4*[0.6*inch]) + t.setStyle([('ALIGN',(1,1),(-2,-2),'LEFT'), + ('TEXTCOLOR',(1,1),(-2,-2),colors.red), + + ('VALIGN',(0,0),(1,-1),'TOP'), + ('ALIGN',(0,0),(1,-1),'RIGHT'), + ('TEXTCOLOR',(0,0),(1,-1),colors.blue), + + ('ALIGN',(0,-1),(-1,-1),'CENTER'), + ('VALIGN',(0,-1),(-1,-1),'MIDDLE'), + ('TEXTCOLOR',(0,-1),(-1,-1),colors.green), + ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), + ('BOX', (0,0), (-1,-1), 0.25, colors.black), + ]) + lst.append(t) + data = [('alignment', 'align\012alignment'), + ('bulletColor', 'bulletcolor\012bcolor'), + ('bulletFontName', 'bfont\012bulletfontname'), + ('bulletFontSize', 'bfontsize\012bulletfontsize'), + ('bulletIndent', 'bindent\012bulletindent'), + ('firstLineIndent', 'findent\012firstlineindent'), + ('fontName', 'face\012fontname\012font'), + ('fontSize', 'size\012fontsize'), + ('leading', 'leading'), + ('leftIndent', 'leftindent\012lindent'), + ('rightIndent', 'rightindent\012rindent'), + ('spaceAfter', 'spaceafter\012spacea'), + ('spaceBefore', 'spacebefore\012spaceb'), + ('textColor', 'fg\012textcolor\012color')] + t = Table(data) + t.setStyle([ + ('VALIGN',(0,0),(-1,-1),'TOP'), + ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), + ('BOX', (0,0), (-1,-1), 0.25, colors.black), + ]) + lst.append(t) + t = Table([ ('Attribute', 'Synonyms'), + ('alignment', 'align, alignment'), + ('bulletColor', 'bulletcolor, bcolor'), + ('bulletFontName', 'bfont, bulletfontname'), + ('bulletFontSize', 'bfontsize, bulletfontsize'), + ('bulletIndent', 'bindent, bulletindent'), + ('firstLineIndent', 'findent, firstlineindent'), + ('fontName', 'face, fontname, font'), + ('fontSize', 'size, fontsize'), + ('leading', 'leading'), + ('leftIndent', 'leftindent, lindent'), + ('rightIndent', 'rightindent, rindent'), + ('spaceAfter', 'spaceafter, spacea'), + ('spaceBefore', 'spacebefore, spaceb'), + ('textColor', 'fg, textcolor, color')]) + t.repeatRows = 1 + t.setStyle([ + ('FONT',(0,0),(-1,1),'Times-Bold',10,12), + ('FONT',(0,1),(-1,-1),'Courier',8,8), + ('VALIGN',(0,0),(-1,-1),'MIDDLE'), + ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), + ('BOX', (0,0), (-1,-1), 0.25, colors.black), + ('BACKGROUND', (0, 0), (-1, 0), colors.green), + ('BACKGROUND', (0, 1), (-1, -1), colors.pink), + ('ALIGN', (0, 0), (-1, 0), 'CENTER'), + ('ALIGN', (0, 1), (0, -1), 'LEFT'), + ('ALIGN', (-1, 1), (-1, -1), 'RIGHT'), + ('FONT', (0, 0), (-1, 0), 'Times-Bold', 12), + ('ALIGN', (1, 1), (1, -1), 'CENTER'), + ]) + lst.append(t) + lst.append(Table(XY, + style=[ ('FONT',(0,0),(-1,-1),'Times-Roman', 5,6), + ('GRID', (0,0), (-1,-1), 0.25, colors.blue),])) + lst.append(Table(XY, + style=[ ('FONT',(0,0),(-1,-1),'Times-Roman', 10,12), + ('GRID', (0,0), (-1,-1), 0.25, colors.black),])) + lst.append(Table(XY, + style=[ ('FONT',(0,0),(-1,-1),'Times-Roman', 20,24), + ('GRID', (0,0), (-1,-1), 0.25, colors.red),])) + lst.append(PageBreak()) + data= [['00', '01', '02', '03', '04'], + ['10', '11', '12', '13', '14'], + ['20', '21', '22', '23', '24'], + ['30', '31', '32', '33', '34']] + t=Table(data,style=[ + ('GRID',(0,0),(-1,-1),0.5,colors.grey), + ('GRID',(1,1),(-2,-2),1,colors.green), + ('BOX',(0,0),(1,-1),2,colors.red), + ('BOX',(0,0),(-1,-1),2,colors.black), + ('LINEABOVE',(1,2),(-2,2),1,colors.blue), + ('LINEBEFORE',(2,1),(2,-2),1,colors.pink), + ('BACKGROUND', (0, 0), (0, 1), colors.pink), + ('BACKGROUND', (1, 1), (1, 2), colors.lavender), + ('BACKGROUND', (2, 2), (2, 3), colors.orange), + ]) + lst.append(Paragraph("Illustrating splits: nosplit", styleSheet['BodyText'])) + lst.append(t) + lst.append(Spacer(0,6)) + lst.append(Paragraph("Illustrating splits: split(4in,30)", styleSheet['BodyText'])) + for s in t.split(4*inch,30): + lst.append(s) + lst.append(Spacer(0,6)) + lst.append(Spacer(0,6)) + lst.append(Paragraph("Illustrating splits: split(4in,36)", styleSheet['BodyText'])) + for s in t.split(4*inch,36): + lst.append(s) + lst.append(Spacer(0,6)) + lst.append(Paragraph("Illustrating splits: split(4in,56)", styleSheet['BodyText'])) + lst.append(Spacer(0,6)) + for s in t.split(4*inch,56): + lst.append(s) + lst.append(Spacer(0,6)) + + lst.append(PageBreak()) + data= [['00', '01', '02', '03', '04'], + ['', '11', '12', '13', '14'], + ['20', '21', '22', '23', '24'], + ['30', '31', '', '33', '34']] + sty=[ + ('GRID',(0,0),(-1,-1),0.5,colors.grey), + ('GRID',(1,1),(-2,-2),1,colors.green), + ('BOX',(0,0),(1,-1),2,colors.red), + ('BOX',(0,0),(-1,-1),2,colors.black), + ('LINEABOVE',(1,2),(-2,2),1,colors.blue), + ('LINEBEFORE',(2,1),(2,-2),1,colors.pink), + ('BACKGROUND', (0, 0), (0, 1), colors.pink), + ('SPAN',(0,0),(0,1)), + ('BACKGROUND', (2, 2), (2, 3), colors.orange), + ('SPAN',(2,2),(2,3)), + ] + t=Table(data,style=sty) + lst.append(Paragraph("Illustrating splits with spans: nosplit", styleSheet['BodyText'])) + lst.append(t) + lst.append(Spacer(0,6)) + lst.append(Paragraph("Illustrating splits with spans: split(4in,30)", styleSheet['BodyText'])) + for s in t.split(4*inch,30): + lst.append(s) + lst.append(Spacer(0,6)) + lst.append(Spacer(0,6)) + lst.append(Paragraph("Illustrating splits with spans: split(4in,36)", styleSheet['BodyText'])) + for s in t.split(4*inch,36): + lst.append(s) + lst.append(Spacer(0,6)) + lst.append(Paragraph("Illustrating splits with spans: split(4in,56)", styleSheet['BodyText'])) + lst.append(Spacer(0,6)) + for s in t.split(4*inch,56): + lst.append(s) + lst.append(Spacer(0,6)) + + data= [['00', '01', '02', '03', '04'], + ['', '11', '12', '13', ''], + ['20', '21', '22', '23', '24'], + ['30', '31', '', '33', ''], + ['40', '41', '', '43', '44']] + sty=[ + ('GRID',(0,0),(-1,-1),0.5,colors.grey), + ('GRID',(1,1),(-2,-2),1,colors.green), + ('BOX',(0,0),(1,-1),2,colors.red), + ('BOX',(0,0),(-1,-1),2,colors.black), + ('LINEABOVE',(1,2),(-2,2),1,colors.blue), + ('LINEBEFORE',(2,1),(2,-2),1,colors.pink), + ('BACKGROUND', (0, 0), (0, 1), colors.pink), + ('SPAN',(0,0),(0,1)), + ('BACKGROUND',(-2,1),(-1,1),colors.palegreen), + ('SPAN',(-2,1),(-1,1)), + ('BACKGROUND',(-2,3),(-1,3),colors.yellow), + ('SPAN',(-2,3),(-1,3)), + ('BACKGROUND', (2, 3), (2, 4), colors.orange), + ('SPAN',(2,3),(2,4)), + ] + + t=Table(data,style=sty,repeatRows=2) + lst.append(Paragraph("Illustrating splits with spans and repeatRows: nosplit", styleSheet['BodyText'])) + lst.append(t) + lst.append(Spacer(0,6)) + if 0: + lst.append(Paragraph("Illustrating splits with spans and repeatRows: split(4in,30)", styleSheet['BodyText'])) + for s in t.split(4*inch,30): + lst.append(s) + lst.append(Spacer(0,6)) + lst.append(Spacer(0,6)) + lst.append(Paragraph("Illustrating splits with spans and repeatRows: split(4in,36)", styleSheet['BodyText'])) + for s in t.split(4*inch,36): + lst.append(s) + lst.append(Spacer(0,6)) + lst.append(Paragraph("Illustrating splits with spans and repeatRows: split(4in,56)", styleSheet['BodyText'])) + lst.append(Spacer(0,6)) + t=Table(data,style=sty,repeatRows=2) + for s in t.split(4*inch,56): + lst.append(s) + lst.append(Spacer(0,6)) + + lst.append(PageBreak()) + import os, reportlab.platypus + I = Image(os.path.join(os.path.dirname(reportlab.platypus.__file__),'..','tools','pythonpoint','demos','leftlogo.gif')) + I.drawHeight = 1.25*inch*I.drawHeight / I.drawWidth + I.drawWidth = 1.25*inch + #I.drawWidth = 9.25*inch #uncomment to see better messaging + P = Paragraph("The ReportLab Left Logo Image", styleSheet["BodyText"]) + data= [['A', 'B', 'C', Paragraph("A paragraph1",styleSheet["BodyText"]), 'D'], + ['00', '01', '02', [I,P], '04'], + ['10', '11', '12', [I,P], '14'], + ['20', '21', '22', '23', '24'], + ['30', '31', '32', '33', '34']] + + t=Table(data,style=[('GRID',(1,1),(-2,-2),1,colors.green), + ('BOX',(0,0),(1,-1),2,colors.red), + ('LINEABOVE',(1,2),(-2,2),1,colors.blue), + ('LINEBEFORE',(2,1),(2,-2),1,colors.pink), + ('BACKGROUND', (0, 0), (0, 1), colors.pink), + ('BACKGROUND', (1, 1), (1, 2), colors.lavender), + ('BACKGROUND', (2, 2), (2, 3), colors.orange), + ('BOX',(0,0),(-1,-1),2,colors.black), + ('GRID',(0,0),(-1,-1),0.5,colors.black), + ('VALIGN',(3,0),(3,0),'BOTTOM'), + ('BACKGROUND',(3,0),(3,0),colors.limegreen), + ('BACKGROUND',(3,1),(3,1),colors.khaki), + ('ALIGN',(3,1),(3,1),'CENTER'), + ('BACKGROUND',(3,2),(3,2),colors.beige), + ('ALIGN',(3,2),(3,2),'LEFT'), + ]) + + t._argW[3]=1.5*inch + lst.append(t) + + # now for an attempt at column spanning. + lst.append(PageBreak()) + data= [['A', 'BBBBB', 'C', 'D', 'E'], + ['00', '01', '02', '03', '04'], + ['10', '11', '12', '13', '14'], + ['20', '21', '22', '23', '24'], + ['30', '31', '32', '33', '34']] + sty = [ + ('ALIGN',(0,0),(-1,-1),'CENTER'), + ('VALIGN',(0,0),(-1,-1),'TOP'), + ('GRID',(0,0),(-1,-1),1,colors.green), + ('BOX',(0,0),(-1,-1),2,colors.red), + + #span 'BBBB' across middle 3 cells in top row + ('SPAN',(1,0),(3,0)), + #now color the first cell in this range only, + #i.e. the one we want to have spanned. Hopefuly + #the range of 3 will come out khaki. + ('BACKGROUND',(1,0),(1,0),colors.khaki), + + ('SPAN',(0,2),(-1,2)), + + + #span 'AAA'down entire left column + ('SPAN',(0,0), (0, 1)), + ('BACKGROUND',(0,0),(0,0),colors.cyan), + ('LINEBELOW', (0,'splitlast'), (-1,'splitlast'), 1, colors.white,'butt'), + ] + t=Table(data,style=sty, colWidths = [20] * 5, rowHeights = [20]*5) + lst.append(t) + + # now for an attempt at percentage widths + lst.append(Spacer(18,18)) + lst.append(Paragraph("This table has colWidths=5*['14%']!", styleSheet['BodyText'])) + t=Table(data,style=sty, colWidths = ['14%'] * 5, rowHeights = [20]*5) + lst.append(t) + + lst.append(Spacer(18,18)) + lst.append(Paragraph("This table has colWidths=['14%','10%','19%','22%','*']!", styleSheet['BodyText'])) + t=Table(data,style=sty, colWidths = ['14%','10%','19%','22%','*'], rowHeights = [20]*5) + lst.append(t) + + # Mike's test example + lst.append(Spacer(18,18)) + lst.append(Paragraph('Mike\'s Spanning Example', styleSheet['Heading1'])) + data= [[Paragraph('World Domination: The First Five Years', styleSheet['BodyText']), ''], + [Paragraph('World Domination: The First Five Years', styleSheet['BodyText']),''], + [Paragraph('World Domination: The First Five Years', styleSheet['BodyText']), ''], + ] + t=Table(data, style=[('SPAN',(0,0),(1,0)),('SPAN',(0,1),(1,1)),('SPAN',(0,2),(1,2)),], colWidths = [3*cm,8*cm], rowHeights = [None]*3) + lst.append(t) + + lst.append(Spacer(18,18)) + lst.append(Paragraph('Mike\'s Non-spanning Example', styleSheet['Heading1'])) + data= [[Paragraph('World Domination: The First Five Years', styleSheet['BodyText'])], + [Paragraph('World Domination: The First Five Years', styleSheet['BodyText'])], + [Paragraph('World Domination: The First Five Years', styleSheet['BodyText'])], + ] + t=Table(data, style=[], colWidths = [11*cm], rowHeights = [None]*3) + lst.append(t) + + lst.append(Spacer(18,18)) + lst.append(Paragraph('xpre example', styleSheet['Heading1'])) + data= [ [ + XPreformatted('Account Details', styleSheet['Heading3']), + '', XPreformatted('Client Details', styleSheet['Heading3']), + ], #end of row 0 + ] + t=Table(data, style=[], colWidths = [80,230.0,80], rowHeights = [None]*1) + lst.append(t) + + lst.append(PageBreak()) + + lst.append(Paragraph('Trying colour cycling in background', styleSheet['Heading1'])) + lst.append(Paragraph("This should alternate pale blue and uncolored by row", styleSheet['BodyText'])) + data= [['001', '01', '02', '03', '04', '05'], + ['002', '01', '02', '03', '04', '05'], + ['003', '01', '02', '03', '04', '05'], + ['004', '01', '02', '03', '04', '05'], + ['005', '01', '02', '03', '04', '05'], + ['006', '01', '02', '03', '04', '05'], + ['007', '01', '02', '03', '04', '05'], + ['008', '01', '02', '03', '04', '05'], + ['009', '01', '02', '03', '04', '05'], + ['010', '01', '02', '03', '04', '05'], + + ] + t=Table(data,style=[ + ('GRID',(0,0),(-1,-1),0.5,colors.grey), + ('ROWBACKGROUNDS', (0, 0), (-1, -1), (0xD0D0FF, None)), + ]) + lst.append(t) + lst.append(Spacer(0,6)) + lst.append(Paragraph("And this should pale blue, pale pink and None by column", styleSheet['BodyText'])) + data= [['001', '01', '02', '03', '04', '05'], + ['002', '01', '02', '03', '04', '05'], + ['003', '01', '02', '03', '04', '05'], + ['004', '01', '02', '03', '04', '05'], + ['005', '01', '02', '03', '04', '05'], + ['006', '01', '02', '03', '04', '05'], + ['007', '01', '02', '03', '04', '05'], + ['008', '01', '02', '03', '04', '05'], + ['009', '01', '02', '03', '04', '05'], + ['010', '01', '02', '03', '04', '05'], + + ] + t=Table(data,style=[ + ('GRID',(0,0),(-1,-1),0.5,colors.grey), + ('COLBACKGROUNDS', (0, 0), (-1, -1), (0xD0D0FF, 0xFFD0D0, None)), + ]) + lst.append(t) + + lst.append(PageBreak()) + lst.append(Paragraph("This spanning example illustrates automatic removal of grids and lines in spanned cells!", styleSheet['BodyText'])) + lst.append(Spacer(0,6)) + data= [['Top\nLeft', '', '02', '03', '04', '05', '06', '07'], + ['', '', '12', 'Span (3,1) (6,2)', '','','','17'], + ['20', '21', '22', '', '','','','27'], + ['30', '31', '32', '33', '34','35','36','37'], + ['40', 'In The\nMiddle', '', '', '44','45','46','47'], + ['50', '', '', '', '54','55','56','57'], + ['60', '', '', '','64', '65', 'Bottom\nRight', ''], + ['70', '71', '72', '73','74', '75', '', '']] + t=Table(data,style=[ + ('GRID',(0,0),(-1,-1),0.5,colors.grey), + ('BACKGROUND',(0,0),(1,1),colors.palegreen), + ('SPAN',(0,0),(1,1)), + ('BACKGROUND',(-2,-2),(-1,-1), colors.pink), + ('SPAN',(-2,-2),(-1,-1)), + ('SPAN',(1,4),(3,6)), + ('BACKGROUND',(1,4),(3,6), colors.lightblue), + ('SPAN',(3,1),(6,2)), + ('BACKGROUND',(3,1),(6,2), colors.peachpuff), + ('VALIGN',(3,1),(6,2),'TOP'), + ('LINEABOVE', (0,2),(-1,2), 1, colors.black, 0, None, None, 2, 2), + ('LINEBEFORE', (3,0),(3,-1), 1, colors.black, 0, None, None, 2, 2), + ]) + lst.append(t) + + lst.append(PageBreak()) + + lst.append(Paragraph("und jetzt noch eine Tabelle mit 5000 Zeilen:", styleSheet['BodyText'])) + sty = [ ('GRID',(0,0),(-1,-1),1,colors.green), + ('BOX',(0,0),(-1,-1),2,colors.red), + ] + data = [[str(i), Paragraph("xx "* (i%10), styleSheet["BodyText"]), Paragraph("blah "*(i%40), styleSheet["BodyText"])] for i in xrange(500)] + t=LongTable(data, style=sty, colWidths = [50,100,200]) + lst.append(t) + + SimpleDocTemplate(outputfile('tables.pdf'), showBoundary=1).build(lst) + + +class TablesTestCase(unittest.TestCase): + "Make documents with tables" + + def test0(self): + "Make a document full of tables" + run() + + def test1(self): + "Make a document full of tables" + old_tables_test() + + +def makeSuite(): + return makeSuiteForClasses(TablesTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_platypus_toc.py b/bin/reportlab/test/test_platypus_toc.py new file mode 100644 index 00000000000..7edb25a2b16 --- /dev/null +++ b/bin/reportlab/test/test_platypus_toc.py @@ -0,0 +1,183 @@ +#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/test/test_platypus_toc.py +"""Tests for the Platypus TableOfContents class. + +Currently there is only one such test. Most such tests, like this +one, will be generating a PDF document that needs to be eye-balled +in order to find out if it is 'correct'. +""" + + +import sys, os +from os.path import join, basename, splitext +from math import sqrt + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.lib.units import inch, cm +from reportlab.lib.pagesizes import A4 +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.xpreformatted import XPreformatted +from reportlab.platypus.frames import Frame +from reportlab.platypus.doctemplate \ + import PageTemplate, BaseDocTemplate +from reportlab.platypus import tableofcontents +from reportlab.platypus.tableofcontents import TableOfContents +from reportlab.platypus.tables import TableStyle, Table +from reportlab.lib import randomtext + + +def myMainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + + canvas.rect(2.5*cm, 2.5*cm, 15*cm, 25*cm) + canvas.setFont('Times-Roman', 12) + pageNumber = canvas.getPageNumber() + canvas.drawString(10*cm, cm, str(pageNumber)) + + canvas.restoreState() + + +class MyDocTemplate(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) + template = PageTemplate('normal', [frame1], myMainPageFrame) + self.addPageTemplates(template) + + + def afterFlowable(self, flowable): + "Registers TOC entries and makes outline entries." + + if flowable.__class__.__name__ == 'Paragraph': + styleName = flowable.style.name + if styleName[:7] == 'Heading': + # Register TOC entries. + level = int(styleName[7:]) + text = flowable.getPlainText() + pageNum = self.page + self.notify('TOCEntry', (level, text, pageNum)) + + # Add PDF outline entries (not really needed/tested here). + key = str(hash(flowable)) + c = self.canv + c.bookmarkPage(key) + c.addOutlineEntry(text, key, level=level, closed=0) + + +def makeHeaderStyle(level, fontName='Times-Roman'): + "Make a header style for different levels." + + assert level >= 0, "Level must be >= 0." + + PS = ParagraphStyle + size = 24.0 / sqrt(1+level) + style = PS(name = 'Heading' + str(level), + fontName = fontName, + fontSize = size, + leading = size*1.2, + spaceBefore = size/4.0, + spaceAfter = size/8.0) + + return style + + +def makeBodyStyle(): + "Body text style - the default will do" + return ParagraphStyle('body') + + +def makeTocHeaderStyle(level, delta, epsilon, fontName='Times-Roman'): + "Make a header style for different levels." + + assert level >= 0, "Level must be >= 0." + + PS = ParagraphStyle + size = 12 + style = PS(name = 'Heading' + str(level), + fontName = fontName, + fontSize = size, + leading = size*1.2, + spaceBefore = size/4.0, + spaceAfter = size/8.0, + firstLineIndent = -epsilon, + leftIndent = level*delta + epsilon) + + return style + + +class TocTestCase(unittest.TestCase): + "Test TableOfContents class (eyeball-test)." + + def test0(self): + """Test story with TOC and a cascaded header hierarchy. + + The story should contain exactly one table of contents that is + immediatly followed by a list of of cascaded levels of header + lines, each nested one level deeper than the previous one. + + Features to be visually confirmed by a human being are: + + 1. TOC lines are indented in multiples of 1 cm. + 2. Wrapped TOC lines continue with additional 0.5 cm indentation. + 3. ... + """ + + maxLevels = 12 + + # Create styles to be used for document headers + # on differnet levels. + headerLevelStyles = [] + for i in range(maxLevels): + headerLevelStyles.append(makeHeaderStyle(i)) + + # Create styles to be used for TOC entry lines + # for headers on differnet levels. + tocLevelStyles = [] + d, e = tableofcontents.delta, tableofcontents.epsilon + for i in range(maxLevels): + tocLevelStyles.append(makeTocHeaderStyle(i, d, e)) + + # Build story. + story = [] + styleSheet = getSampleStyleSheet() + bt = styleSheet['BodyText'] + + description = '%s' % self.test0.__doc__ + story.append(XPreformatted(description, bt)) + + toc = TableOfContents() + toc.levelStyles = tocLevelStyles + story.append(toc) + + for i in range(maxLevels): + story.append(Paragraph('HEADER, LEVEL %d' % i, + headerLevelStyles[i])) + #now put some body stuff in. + txt = randomtext.randomText(randomtext.PYTHON, 5) + para = Paragraph(txt, makeBodyStyle()) + story.append(para) + + path = outputfile('test_platypus_toc.pdf') + doc = MyDocTemplate(path) + doc.multiBuild(story) + + +def makeSuite(): + return makeSuiteForClasses(TocTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_platypus_xref.py b/bin/reportlab/test/test_platypus_xref.py new file mode 100644 index 00000000000..0b61139c20e --- /dev/null +++ b/bin/reportlab/test/test_platypus_xref.py @@ -0,0 +1,140 @@ +#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/test/test_platypus_xref.py +"""Test long documents with indexes, tables and cross-references +""" + +import sys, os, time +from string import split, strip, join, whitespace, find +from operator import truth +from types import StringType, ListType + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.lib import colors +from reportlab.lib.units import cm +from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus import Paragraph, Flowable, Frame, PageTemplate, BaseDocTemplate +from reportlab.platypus.frames import Frame +from reportlab.lib.randomtext import randomText, PYTHON +from reportlab.platypus.tableofcontents import TableOfContents, SimpleIndex + + +def myMainPageFrame(canvas, doc): + "The page frame used for all PDF documents." + + canvas.saveState() + canvas.setFont('Times-Roman', 12) + pageNumber = canvas.getPageNumber() + canvas.drawString(10*cm, cm, str(pageNumber)) + canvas.restoreState() + + +class MyDocTemplate(BaseDocTemplate): + _invalidInitArgs = ('pageTemplates',) + + def __init__(self, filename, **kw): + frame1 = Frame(2.5*cm, 2.5*cm, 16*cm, 25*cm, id='Frame1') + self.allowSplitting = 0 + self.showBoundary = 1 + apply(BaseDocTemplate.__init__, (self, filename), kw) + template = PageTemplate('normal', [frame1], myMainPageFrame) + self.addPageTemplates(template) + + def afterFlowable(self, flowable): + "Registers TOC and Index entries and makes outline entries." + if flowable.__class__.__name__ == 'Paragraph': + styleName = flowable.style.name + if styleName == 'Heading1': + level = 0 + text = flowable.getPlainText() + pageNum = self.page + self.notify('TOCEntry', (level, text, pageNum)) + + # Add PDF outline entries (not really needed/tested here). + key = str(hash(flowable)) + c = self.canv + c.bookmarkPage(key) + c.addOutlineEntry(text, key, level=level, closed=0) + + # index a bunch of pythonic buzzwords. In real life this + # would be driven by markup. + try: + text = flowable.getPlainText() + except: + return + for phrase in ['uniform','depraved','finger', 'Fraudulin']: + if find(text, phrase) > -1: + self.notify('IndexEntry', (phrase, self.page)) + #print 'IndexEntry:',phrase, self.page + + +def _test0(self): + "This makes one long multi-page paragraph." + + # Build story. + story = [] + + styleSheet = getSampleStyleSheet() + h1 = styleSheet['Heading1'] + h1.pageBreakBefore = 1 + h1.keepWithNext = 1 + h1.outlineLevel = 0 + + h2 = styleSheet['Heading2'] + h2.backColor = colors.cyan + h2.keepWithNext = 1 + h2.outlineLevel = 1 + + bt = styleSheet['BodyText'] + + story.append(Paragraph("""Cross-Referencing Test""", styleSheet["Title"])) + story.append(Paragraph(""" + Subsequent pages test cross-references: indexes, tables and individual + cross references. The number in brackets at the end of each paragraph + is its position in the story. (%d)""" % len(story), bt)) + + story.append(Paragraph("""Table of Contents:""", styleSheet["Title"])) + toc = TableOfContents() + story.append(toc) + + chapterNum = 1 + for i in range(10): + story.append(Paragraph('Chapter %d: Chapters always starts a new page' % chapterNum, h1)) + chapterNum = chapterNum + 1 + for j in range(3): + story.append(Paragraph('Heading1 paragraphs should always' + 'have a page break before. Heading 2 on the other hand' + 'should always have a FRAME break before (%d)' % len(story), bt)) + story.append(Paragraph('Heading 2 should always be kept with the next thing (%d)' % len(story), h2)) + for j in range(3): + story.append(Paragraph(randomText(theme=PYTHON, sentences=2)+' (%d)' % len(story), bt)) + story.append(Paragraph('I should never be at the bottom of a frame (%d)' % len(story), h2)) + story.append(Paragraph(randomText(theme=PYTHON, sentences=1)+' (%d)' % len(story), bt)) + + story.append(Paragraph('The Index which goes at the back', h1)) + story.append(SimpleIndex()) + + doc = MyDocTemplate(outputfile('test_platypus_xref.pdf')) + doc.multiBuild(story) + + +class BreakingTestCase(unittest.TestCase): + "Test multi-page splitting of paragraphs (eyeball-test)." + def test0(self): + _test0(self) + + +def makeSuite(): + return makeSuiteForClasses(BreakingTestCase) + + +#noruntests +if __name__ == "__main__": + if 'debug' in sys.argv: + _test1(None) + else: + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_pyfiles.py b/bin/reportlab/test/test_pyfiles.py new file mode 100644 index 00000000000..8a7393ee7d0 --- /dev/null +++ b/bin/reportlab/test/test_pyfiles.py @@ -0,0 +1,158 @@ +#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/test/test_pyfiles.py +"""Tests performed on all Python source files of the ReportLab distribution. +""" + + +import os, sys, string, fnmatch, re + +import reportlab +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, SecureTestCase, GlobDirectoryWalker, outputfile, printLocation +from reportlab.lib.utils import open_and_read, open_and_readlines + +RL_HOME = os.path.dirname(reportlab.__file__) + + +# Helper function and class. + +def unique(seq): + "Remove elements from a list that occur more than once." + + # Return input if it has less than 2 elements. + if len(seq) < 2: + return seq + + # Make a sorted copy of the input sequence. + seq2 = seq[:] + if type(seq2) == type(''): + seq2 = map(None, seq2) + seq2.sort() + + # Remove adjacent elements if they are identical. + i = 0 + while i < len(seq2)-1: + elem = seq2[i] + try: + while elem == seq2[i+1]: + del seq2[i+1] + except IndexError: + pass + i = i + 1 + + # Try to return something of the same type as the input. + if type(seq) == type(''): + return string.join(seq2, '') + else: + return seq2 + +class SelfTestCase(unittest.TestCase): + "Test unique() function." + + def testUnique(self): + "Test unique() function." + + cases = [([], []), + ([0], [0]), + ([0, 1, 2], [0, 1, 2]), + ([2, 1, 0], [0, 1, 2]), + ([0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 3]), + ('abcabcabc', 'abc') + ] + + msg = "Failed: unique(%s) returns %s instead of %s." + for sequence, expectedOutput in cases: + output = unique(sequence) + args = (sequence, output, expectedOutput) + assert output == expectedOutput, msg % args + + +class AsciiFileTestCase(unittest.TestCase): + "Test if Python files are pure ASCII ones." + + def testAscii(self): + "Test if Python files are pure ASCII ones." + + RL_HOME = os.path.dirname(reportlab.__file__) + allPyFiles = GlobDirectoryWalker(RL_HOME, '*.py') + + for path in allPyFiles: + fileContent = open_and_read(path,'r') + nonAscii = filter(lambda c: ord(c)>127, fileContent) + nonAscii = unique(nonAscii) + + truncPath = path[string.find(path, 'reportlab'):] + args = (truncPath, repr(map(ord, nonAscii))) + msg = "File %s contains characters: %s." % args +## if nonAscii: +## print msg + assert nonAscii == '', msg + + +class FilenameTestCase(unittest.TestCase): + "Test if Python files contain trailing digits." + + def testTrailingDigits(self): + "Test if Python files contain trailing digits." + + allPyFiles = GlobDirectoryWalker(RL_HOME, '*.py') + + for path in allPyFiles: + #hack - exclude barcode extensions from this test + if string.find(path, 'barcode'): + pass + else: + basename = os.path.splitext(path)[0] + truncPath = path[string.find(path, 'reportlab'):] + msg = "Filename %s contains trailing digits." % truncPath + assert basename[-1] not in string.digits, msg + + ## if basename[-1] in string.digits: + ## print truncPath + + +class FirstLineTestCase(SecureTestCase): + "Testing if objects in the ReportLab package have docstrings." + + def findSuspiciousModules(self, folder, rootName): + "Get all modul paths with non-Unix-like first line." + + firstLinePat = re.compile('^#!.*python.*') + + paths = [] + for file in GlobDirectoryWalker(folder, '*.py'): + if os.path.basename(file) == '__init__.py': + continue + firstLine = open_and_readlines(file)[0] + if not firstLinePat.match(firstLine): + paths.append(file) + + return paths + + def test1(self): + "Test if all Python files have a Unix-like first line." + + path = outputfile("test_firstline.log") + file = open(path, 'w') + file.write('No Unix-like first line found in the files below.\n\n') + + paths = self.findSuspiciousModules(RL_HOME, 'reportlab') + paths.sort() + + for p in paths: + file.write("%s\n" % p) + + file.close() + +def makeSuite(): + suite = makeSuiteForClasses(SelfTestCase, AsciiFileTestCase, FilenameTestCase) + if sys.platform[:4] != 'java': + loader = unittest.TestLoader() + suite.addTest(loader.loadTestsFromTestCase(FirstLineTestCase)) + return suite + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_renderSVG.py b/bin/reportlab/test/test_renderSVG.py new file mode 100755 index 00000000000..a2d5c1cd77e --- /dev/null +++ b/bin/reportlab/test/test_renderSVG.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python + +import sys, string +from xml.dom import minidom +from xml.sax._exceptions import SAXReaderNotAvailable + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.graphics.shapes import * +from reportlab.graphics import renderSVG + + + + +def warnIgnoredRestofTest(): + "Raise a warning (if possible) about a not fully completed test." + + version = sys.version_info[:2] + msg = "XML parser not found - consider installing expat! Rest of test(s) ignored!" + if version >= (2, 1): + import warnings + warnings.warn(msg) + else: + # should better also be printed only once... + print msg + + + + +# Check if we have a default XML parser available or not. + +try: + import xml + from xml.sax import make_parser + p = xml.sax.make_parser() + HAVE_XML_PARSER = 1 +except SAXReaderNotAvailable: + HAVE_XML_PARSER = 0 + + + + +def load(path): + "Helper function to read the generated SVG again." + + doc = minidom.parse(path) + doc.normalize() + return doc.documentElement + + + + +class RenderSvgSimpleTestCase(unittest.TestCase): + "Testing renderSVG module." + + def test0(self): + "Test two strings in drawing." + + path = outputfile("test_renderSVG_simple_test0.svg") + + d = Drawing(200, 100) + d.add(String(0, 0, "foo")) + d.add(String(100, 0, "bar")) + renderSVG.drawToFile(d, path) + + if not HAVE_XML_PARSER: + warnIgnoredRestofTest() + return + + svg = load(path) + fg = svg.getElementsByTagName('g')[0] # flipping group + dg = fg.getElementsByTagName('g')[0] # diagram group + textChildren = dg.getElementsByTagName('text') # text nodes + t0 = string.strip(textChildren[0].childNodes[0].nodeValue) + t1 = string.strip(textChildren[1].childNodes[0].nodeValue) + assert t0 == 'foo' + assert t1 == 'bar' + + + def test1(self): + "Test two strings in group in drawing." + + path = outputfile("test_renderSVG_simple_test1.svg") + + d = Drawing(200, 100) + g = Group() + g.add(String(0, 0, "foo")) + g.add(String(100, 0, "bar")) + d.add(g) + renderSVG.drawToFile(d, path) + + if not HAVE_XML_PARSER: + warnIgnoredRestofTest() + return + + svg = load(path) + fg = svg.getElementsByTagName('g')[0] # flipping group + dg = fg.getElementsByTagName('g')[0] # diagram group + g = dg.getElementsByTagName('g')[0] # custom group + textChildren = g.getElementsByTagName('text') # text nodes + t0 = string.strip(textChildren[0].childNodes[0].nodeValue) + t1 = string.strip(textChildren[1].childNodes[0].nodeValue) + + assert t0 == 'foo' + assert t1 == 'bar' + + + def test2(self): + "Test two strings in transformed group in drawing." + + path = outputfile("test_renderSVG_simple_test2.svg") + + d = Drawing(200, 100) + g = Group() + g.add(String(0, 0, "foo")) + g.add(String(100, 0, "bar")) + g.scale(1.5, 1.2) + g.translate(50, 0) + d.add(g) + renderSVG.drawToFile(d, path) + + if not HAVE_XML_PARSER: + warnIgnoredRestofTest() + return + + svg = load(path) + fg = svg.getElementsByTagName('g')[0] # flipping group + dg = fg.getElementsByTagName('g')[0] # diagram group + g = dg.getElementsByTagName('g')[0] # custom group + textChildren = g.getElementsByTagName('text') # text nodes + t0 = string.strip(textChildren[0].childNodes[0].nodeValue) + t1 = string.strip(textChildren[1].childNodes[0].nodeValue) + + assert t0 == 'foo' + assert t1 == 'bar' + + + + +class RenderSvgAxesTestCase(unittest.TestCase): + "Testing renderSVG module on Axes widgets." + + def test0(self): + "Test two strings in drawing." + + path = outputfile("axestest0.svg") + from reportlab.graphics.charts.axes import XCategoryAxis + + d = XCategoryAxis().demo() + renderSVG.drawToFile(d, path) + + + + +def makeSuite(): + return makeSuiteForClasses(RenderSvgSimpleTestCase, RenderSvgAxesTestCase) + + + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_rl_accel.py b/bin/reportlab/test/test_rl_accel.py new file mode 100755 index 00000000000..7e7bd5c2467 --- /dev/null +++ b/bin/reportlab/test/test_rl_accel.py @@ -0,0 +1,168 @@ +__version__=''' $Id''' +__doc__='''basic tests.''' + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, printLocation + +def getrc(defns,depth=1): + from sys import getrefcount, _getframe + f = _getframe(depth) + G0 = f.f_globals + L = f.f_locals + if L is not G0: + LL = [L] + while 1: + f = f.f_back + G = f.f_globals + L = f.f_locals + if G is not G0 or G is L: break + LL.append(L) + L = {} + for l in reversed(LL): + L.update(l) + else: + L = L.copy() + G0 = G0.copy() + return [getrefcount(eval(x,L,G0))-1 for x in defns.split()] + +def checkrc(defns,rcv0): + rcv1 = getrc(defns,2) + return ' '.join(["%s %d-->%d" % (x,v,w) for x,v,w in zip(defns.split(),rcv0,rcv1) if v!=w]) + +class RlAccelTestCase(unittest.TestCase): + + def testFpStr(self): + # should give siz decimal places if less than 1. + # if more, give up to seven sig figs + from _rl_accel import fp_str + assert fp_str(1,2,3)=='1 2 3' + assert fp_str(1) == '1' + + assert fp_str(595.275574) == '595.2756' + assert fp_str(59.5275574) == '59.52756' + assert fp_str(5.95275574) == '5.952756' + + def test_AsciiBase85Encode(self): + from _rl_accel import _AsciiBase85Encode + assert _AsciiBase85Encode('Dragan Andric')=='6ul^K@;[2RDIdd%@f~>' + + def test_AsciiBase85Decode(self): + from _rl_accel import _AsciiBase85Decode + assert _AsciiBase85Decode('6ul^K@;[2RDIdd%@f~>')=='Dragan Andric' + + def testEscapePDF(self): + from _rl_accel import escapePDF + assert escapePDF('(test)')=='\\(test\\)' + + def test_instanceEscapePDF(self): + from _rl_accel import _instanceEscapePDF + assert _instanceEscapePDF('', '(test)')=='\\(test\\)' + + def testCalcChecksum(self): + from _rl_accel import calcChecksum + assert calcChecksum('test')==1952805748 + + def testStringWidth(self): + from _rl_accel import stringWidthU + from reportlab.pdfbase.pdfmetrics import _py_stringWidth, getFont, registerFont, _fonts + from reportlab.pdfbase.ttfonts import TTFont + ttfn = 'Luxi-Serif' + t1fn = 'Times-Roman' + registerFont(TTFont(ttfn, "luxiserif.ttf")) + ttf = getFont(ttfn) + t1f = getFont(t1fn) + testCp1252 = 'copyright %s trademark %s registered %s ReportLab! Ol%s!' % (chr(169), chr(153),chr(174), chr(0xe9)) + enc='cp1252' + senc = 'utf8' + intern(senc) + ts = 'ABCDEF\xce\x91\xce\xb2G' + utext = 'ABCDEF\xce\x91\xce\xb2G'.decode('utf8') + fontSize = 12 + defns="ttfn t1fn ttf t1f testCp1252 enc senc ts utext fontSize ttf.face ttf.face.charWidths ttf.face.defaultWidth t1f.widths t1f.encName t1f.substitutionFonts _fonts" + rcv = getrc(defns) + def tfunc(ts,fn,fontSize,enc): + w1 = stringWidthU(ts,fn,fontSize,enc) + w2 = _py_stringWidth(ts,fn,fontSize,enc) + assert abs(w1-w2)<1e-10,"stringWidthU(%r,%r,%s,%r)-->%r != _py_stringWidth(...)-->%r" % (ts,fn,fontSize,enc,w1,w2) + tfunc(testCp1252,t1fn,fontSize,enc) + tfunc(ts,t1fn,fontSize,senc) + tfunc(utext,t1fn,fontSize,senc) + tfunc(ts,ttfn,fontSize,senc) + tfunc(testCp1252,ttfn,fontSize,enc) + tfunc(utext,ttfn,fontSize,senc) + rcc = checkrc(defns,rcv) + assert not rcc, "rc diffs (%s)" % rcc + + def test_instanceStringWidth(self): + from reportlab.pdfbase.pdfmetrics import registerFont, getFont, _fonts, unicode2T1 + from reportlab.pdfbase.ttfonts import TTFont + ttfn = 'Luxi-Serif' + t1fn = 'Times-Roman' + registerFont(TTFont(ttfn, "luxiserif.ttf")) + ttf = getFont(ttfn) + t1f = getFont(t1fn) + testCp1252 = 'copyright %s trademark %s registered %s ReportLab! Ol%s!' % (chr(169), chr(153),chr(174), chr(0xe9)) + enc='cp1252' + senc = 'utf8' + ts = 'ABCDEF\xce\x91\xce\xb2G' + utext = 'ABCDEF\xce\x91\xce\xb2G'.decode(senc) + fontSize = 12 + defns="ttfn t1fn ttf t1f testCp1252 enc senc ts utext fontSize ttf.face ttf.face.charWidths ttf.face.defaultWidth t1f.widths t1f.encName t1f.substitutionFonts _fonts" + rcv = getrc(defns) + def tfunc(f,ts,fontSize,enc): + w1 = f.stringWidth(ts,fontSize,enc) + w2 = f._py_stringWidth(ts,fontSize,enc) + assert abs(w1-w2)<1e-10,"f(%r).stringWidthU(%r,%s,%r)-->%r != f._py_stringWidth(...)-->%r" % (f,ts,fontSize,enc,w1,w2) + tfunc(t1f,testCp1252,fontSize,enc) + tfunc(t1f,ts,fontSize,senc) + tfunc(t1f,utext,fontSize,senc) + tfunc(ttf,ts,fontSize,senc) + tfunc(ttf,testCp1252,fontSize,enc) + tfunc(ttf,utext,fontSize,senc) + rcc = checkrc(defns,rcv) + assert not rcc, "rc diffs (%s)" % rcc + + def test_unicode2T1(self): + from reportlab.pdfbase.pdfmetrics import _py_unicode2T1, getFont, _fonts + from _rl_accel import unicode2T1 + t1fn = 'Times-Roman' + t1f = getFont(t1fn) + enc = 'cp1252' + senc = 'utf8' + testCp1252 = ('copyright %s trademark %s registered %s ReportLab! Ol%s!' % (chr(169), chr(153),chr(174), chr(0xe9))).decode(enc) + utext = 'This is the end of the \xce\x91\xce\xb2 world. This is the end of the \xce\x91\xce\xb2 world jap=\xe3\x83\x9b\xe3\x83\x86. This is the end of the \xce\x91\xce\xb2 world. This is the end of the \xce\x91\xce\xb2 world jap=\xe3\x83\x9b\xe3\x83\x86'.decode('utf8') + def tfunc(f,ts): + w1 = unicode2T1(ts,[f]+f.substitutionFonts) + w2 = _py_unicode2T1(ts,[f]+f.substitutionFonts) + assert w1==w2,"%r != %r" % (w1,w2) + defns="t1fn t1f testCp1252 enc senc utext t1f.widths t1f.encName t1f.substitutionFonts _fonts" + rcv = getrc(defns) + tfunc(t1f,testCp1252) + tfunc(t1f,utext) + rcc = checkrc(defns,rcv) + assert not rcc, "rc diffs (%s)" % rcc + + def test_getFont(self): + from reportlab.pdfbase.pdfmetrics import _py_getFont, getFont + from _rl_accel import getFontU + assert getFontU is getFont + t1fn = 'Times-Roman' + assert _py_getFont(t1fn) is getFontU(t1fn) + + def test_sameFrag(self): + pass + +def makeSuite(): + # only run the tests if _rl_accel is present + try: + import _rl_accel + Klass = RlAccelTestCase + except: + class Klass(unittest.TestCase): + pass + return makeSuiteForClasses(Klass) + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_source_chars.py b/bin/reportlab/test/test_source_chars.py new file mode 100644 index 00000000000..1b4204b7ba1 --- /dev/null +++ b/bin/reportlab/test/test_source_chars.py @@ -0,0 +1,95 @@ +#!/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/test/test_source_chars.py + +"""This tests for things in source files. Initially, absence of tabs :-) +""" + +import os, sys, glob, string, re +from types import ModuleType, ClassType, MethodType, FunctionType + +import reportlab +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, SecureTestCase, GlobDirectoryWalker, printLocation +from reportlab.lib.utils import open_and_read + + +class SourceTester(SecureTestCase): + def setUp(self): + SecureTestCase.setUp(self) + try: + fn = __file__ + except: + fn = sys.argv[0] + + self.output = open(outputfile(os.path.splitext(os.path.basename(fn))[0]+'.txt'),'w') + + def checkFileForTabs(self, filename): + txt = open_and_read(filename, 'r') + chunks = string.split(txt, '\t') + tabCount = len(chunks) - 1 + if tabCount: + #raise Exception, "File %s contains %d tab characters!" % (filename, tabCount) + self.output.write("file %s contains %d tab characters!\n" % (filename, tabCount)) + + def checkFileForTrailingSpaces(self, filename): + txt = open_and_read(filename, 'r') + initSize = len(txt) + badLines = 0 + badChars = 0 + for line in string.split(txt, '\n'): + stripped = string.rstrip(line) + spaces = len(line) - len(stripped) # OK, so they might be trailing tabs, who cares? + if spaces: + badLines = badLines + 1 + badChars = badChars + spaces + + if badChars <> 0: + self.output.write("file %s contains %d trailing spaces, or %0.2f%% wastage\n" % (filename, badChars, 100.0*badChars/initSize)) + + def testFiles(self): + topDir = os.path.dirname(reportlab.__file__) + w = GlobDirectoryWalker(topDir, '*.py') + for filename in w: + self.checkFileForTabs(filename) + self.checkFileForTrailingSpaces(filename) + +def zapTrailingWhitespace(dirname): + """Eliminates trailing spaces IN PLACE. Use with extreme care + and only after a backup or with version-controlled code.""" + assert os.path.isdir(dirname), "Directory not found!" + print "This will eliminate all trailing spaces in py files under %s." % dirname + ok = raw_input("Shall I proceed? type YES > ") + if ok <> 'YES': + print 'aborted by user' + return + w = GlobDirectoryWalker(dirname, '*.py') + for filename in w: + # trim off final newline and detect real changes + txt = open(filename, 'r').read() + badChars = 0 + cleaned = [] + for line in string.split(txt, '\n'): + stripped = string.rstrip(line) + cleaned.append(stripped) + spaces = len(line) - len(stripped) # OK, so they might be trailing tabs, who cares? + if spaces: + badChars = badChars + spaces + + if badChars <> 0: + open(filename, 'w').write(string.join(cleaned, '\n')) + print "file %s contained %d trailing spaces, FIXED" % (filename, badChars) + print 'done' + +def makeSuite(): + return makeSuiteForClasses(SourceTester) + + +#noruntests +if __name__ == "__main__": + if len(sys.argv) == 3 and sys.argv[1] == 'zap' and os.path.isdir(sys.argv[2]): + zapTrailingWhitespace(sys.argv[2]) + else: + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_table_layout.py b/bin/reportlab/test/test_table_layout.py new file mode 100644 index 00000000000..d33be6d01d2 --- /dev/null +++ b/bin/reportlab/test/test_table_layout.py @@ -0,0 +1,425 @@ +import operator, string + +from reportlab.platypus import * +#from reportlab import rl_config +from reportlab.lib.styles import PropertySet, getSampleStyleSheet, ParagraphStyle +from reportlab.lib import colors +from reportlab.platypus.paragraph import Paragraph +#from reportlab.lib.utils import fp_str +#from reportlab.pdfbase import pdfmetrics +from reportlab.platypus.flowables import PageBreak + + +import os + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + + +from types import TupleType, ListType, StringType + + +class TableTestCase(unittest.TestCase): + + + def getDataBlock(self): + "Helper - data for our spanned table" + return [ + # two rows are for headers + ['Region','Product','Period',None,None,None,'Total'], + [None,None,'Q1','Q2','Q3','Q4',None], + + # now for data + ['North','Spam',100,110,120,130,460], + ['North','Eggs',101,111,121,131,464], + ['North','Guinness',102,112,122,132,468], + + ['South','Spam',100,110,120,130,460], + ['South','Eggs',101,111,121,131,464], + ['South','Guinness',102,112,122,132,468], + ] + + def test_document(self): + + rowheights = (24, 16, 16, 16, 16) + rowheights2 = (24, 16, 16, 16, 30) + colwidths = (50, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32) + GRID_STYLE = TableStyle( + [('GRID', (0,0), (-1,-1), 0.25, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT')] + ) + + styleSheet = getSampleStyleSheet() + styNormal = styleSheet['Normal'] + styNormal.spaceBefore = 6 + styNormal.spaceAfter = 6 + + data = ( + ('', 'Jan', 'Feb', 'Mar','Apr','May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), + ('Mugs', 0, 4, 17, 3, 21, 47, 12, 33, 2, -2, 44, 89), + ('T-Shirts', 0, 42, 9, -3, 16, 4, 72, 89, 3, 19, 32, 119), + ('Miscellaneous accessories', 0,0,0,0,0,0,1,0,0,0,2,13), + ('Hats', 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, 453, '1,344','2,843') + ) + data2 = ( + ('', 'Jan', 'Feb', 'Mar','Apr','May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), + ('Mugs', 0, 4, 17, 3, 21, 47, 12, 33, 2, -2, 44, 89), + ('T-Shirts', 0, 42, 9, -3, 16, 4, 72, 89, 3, 19, 32, 119), + ('Key Ring', 0,0,0,0,0,0,1,0,0,0,2,13), + ('Hats\nLarge', 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, 453, '1,344','2,843') + ) + + + data3 = ( + ('', 'Jan', 'Feb', 'Mar','Apr','May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), + ('Mugs', 0, 4, 17, 3, 21, 47, 12, 33, 2, -2, 44, 89), + ('T-Shirts', 0, 42, 9, -3, 16, 4, 72, 89, 3, 19, 32, 119), + ('Key Ring', 0,0,0,0,0,0,1,0,0,0,2,13), + (Paragraph("Let's really mess things up with a paragraph",styNormal), + 893, 912, '1,212', 643, 789, 159, 888, '1,298', 832, 453, '1,344','2,843') + ) + + lst = [] + + + lst.append(Paragraph("""Basics about column sizing and cell contents""", styleSheet['Heading1'])) + + t1 = Table(data, colwidths, rowheights) + t1.setStyle(GRID_STYLE) + lst.append(Paragraph("This is GRID_STYLE with explicit column widths. Each cell contains a string or number\n", styleSheet['BodyText'])) + lst.append(t1) + lst.append(Spacer(18,18)) + + t2 = Table(data, None, None) + t2.setStyle(GRID_STYLE) + lst.append(Paragraph("""This is GRID_STYLE with no size info. It + does the sizes itself, measuring each text string + and computing the space it needs. If the text is + too wide for the frame, the table will overflow + as seen here.""", + styNormal)) + lst.append(t2) + lst.append(Spacer(18,18)) + + t3 = Table(data2, None, None) + t3.setStyle(GRID_STYLE) + lst.append(Paragraph("""This demonstrates the effect of adding text strings with + newlines to a cell. It breaks where you specify, and if rowHeights is None (i.e + automatic) then you'll see the effect. See bottom left cell.""", + styNormal)) + lst.append(t3) + lst.append(Spacer(18,18)) + + + colWidths = (None, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32) + t3 = Table(data3, colWidths, None) + t3.setStyle(GRID_STYLE) + lst.append(Paragraph("""This table does not specify the size of the first column, + so should work out a sane one. In this case the element + at bottom left is a paragraph, which has no intrinsic size + (the height and width are a function of each other). So, + it tots up the extra space in the frame and divides it + between any such unsizeable columns. As a result the + table fills the width of the frame (except for the + 6 point padding on either size).""", + styNormal)) + lst.append(t3) + lst.append(PageBreak()) + + lst.append(Paragraph("""Row and Column spanning""", styleSheet['Heading1'])) + + lst.append(Paragraph("""This shows a very basic table. We do a faint pink grid + to show what's behind it - imagine this is not printed, as we'll overlay it later + with some black lines. We're going to "span" some cells, and have put a + value of None in the data to signify the cells we don't care about. + (In real life if you want an empty cell, put '' in it rather than None). """, styNormal)) + + sty = TableStyle([ + #very faint grid to show what's where + ('GRID', (0,0), (-1,-1), 0.25, colors.pink), + ]) + + t = Table(self.getDataBlock(), colWidths=None, rowHeights=None, style=sty) + lst.append(t) + + + + lst.append(Paragraph("""We now center the text for the "period" + across the four cells for each quarter. To do this we add a 'span' + command to the style to make the cell at row 1 column 3 cover 4 cells, + and a 'center' command for all cells in the top row. The spanning + is not immediately evident but trust us, it's happening - the word + 'Period' is centered across the 4 columns. Note also that the + underlying grid shows through. All line drawing commands apply + to the underlying grid, so you have to take care what you put + grids through.""", styNormal)) + sty = TableStyle([ + # + ('GRID', (0,0), (-1,-1), 0.25, colors.pink), + ('ALIGN', (0,0), (-1,0), 'CENTER'), + ('SPAN', (2,0), (5,0)), + ]) + + t = Table(self.getDataBlock(), colWidths=None, rowHeights=None, style=sty) + lst.append(t) + + lst.append(Paragraph("""We repeat this for the words 'Region', Product' + and 'Total', which each span the top 2 rows; and for 'Nprth' and 'South' + which span 3 rows. At the moment each cell's alignment is the default + (bottom), so these words appear to have "dropped down"; in fact they + are sitting on the bottom of their allocated ranges. You will just see that + all the 'None' values vanished, as those cells are not drawn any more.""", styNormal)) + sty = TableStyle([ + # + ('GRID', (0,0), (-1,-1), 0.25, colors.pink), + ('ALIGN', (0,0), (-1,0), 'CENTER'), + ('SPAN', (2,0), (5,0)), + #span the other column heads down 2 rows + ('SPAN', (0,0), (0,1)), + ('SPAN', (1,0), (1,1)), + ('SPAN', (6,0), (6,1)), + #span the 'north' and 'south' down 3 rows each + ('SPAN', (0,2), (0,4)), + ('SPAN', (0,5), (0,7)), + ]) + + t = Table(self.getDataBlock(), colWidths=None, rowHeights=None, style=sty) + lst.append(t) + + + lst.append(PageBreak()) + + + lst.append(Paragraph("""Now we'll tart things up a bit. First, + we set the vertical alignment of each spanned cell to 'middle'. + Next we add in some line drawing commands which do not slash across + the spanned cells (this needs a bit of work). + Finally we'll add some thicker lines to divide it up, and hide the pink. Voila! + """, styNormal)) + sty = TableStyle([ + # +# ('GRID', (0,0), (-1,-1), 0.25, colors.pink), + ('TOPPADDING', (0,0), (-1,-1), 3), + + #span the 'period' + ('SPAN', (2,0), (5,0)), + #span the other column heads down 2 rows + ('SPAN', (0,0), (0,1)), + ('SPAN', (1,0), (1,1)), + ('SPAN', (6,0), (6,1)), + #span the 'north' and 'south' down 3 rows each + ('SPAN', (0,2), (0,4)), + ('SPAN', (0,5), (0,7)), + + #top row headings are centred + ('ALIGN', (0,0), (-1,0), 'CENTER'), + #everything we span is vertically centred + #span the other column heads down 2 rows + ('VALIGN', (0,0), (0,1), 'MIDDLE'), + ('VALIGN', (1,0), (1,1), 'MIDDLE'), + ('VALIGN', (6,0), (6,1), 'MIDDLE'), + #span the 'north' and 'south' down 3 rows each + ('VALIGN', (0,2), (0,4), 'MIDDLE'), + ('VALIGN', (0,5), (0,7), 'MIDDLE'), + + #numeric stuff right aligned + ('ALIGN', (2,1), (-1,-1), 'RIGHT'), + + #draw lines carefully so as not to swipe through + #any of the 'spanned' cells + ('GRID', (1,2), (-1,-1), 1.0, colors.black), + ('BOX', (0,2), (0,4), 1.0, colors.black), + ('BOX', (0,5), (0,7), 1.0, colors.black), + ('BOX', (0,0), (0,1), 1.0, colors.black), + ('BOX', (1,0), (1,1), 1.0, colors.black), + + ('BOX', (2,0), (5,0), 1.0, colors.black), + ('GRID', (2,1), (5,1), 1.0, colors.black), + + ('BOX', (6,0), (6,1), 1.0, colors.black), + + # do fatter boxes around some cells + ('BOX', (0,0), (-1,1), 2.0, colors.black), + ('BOX', (0,2), (-1,4), 2.0, colors.black), + ('BOX', (0,5), (-1,7), 2.0, colors.black), + ('BOX', (-1,0), (-1,-1), 2.0, colors.black), + + ]) + + t = Table(self.getDataBlock(), colWidths=None, rowHeights=None, style=sty) + lst.append(t) + + lst.append(Paragraph("""How cells get sized""", styleSheet['Heading1'])) + + lst.append(Paragraph("""So far the table has been auto-sized. This can be + computationally expensive, and can lead to yucky effects. Imagine a lot of + numbers, one of which goes to 4 figures - tha numeric column will be wider. + The best approach is to specify the column + widths where you know them, and let the system do the heights. Here we set some + widths - an inch for the text columns and half an inch for the numeric ones. + """, styNormal)) + + t = Table(self.getDataBlock(), + colWidths=(72,72,36,36,36,36,56), + rowHeights=None, + style=sty) + lst.append(t) + + lst.append(Paragraph("""The auto-sized example 2 steps back demonstrates + one advanced feature of the sizing algorithm. In the table below, + the columns for Q1-Q4 should all be the same width. We've made + the text above it a bit longer than "Period". Note that this text + is technically in the 3rd column; on our first implementation this + was sized and column 3 was therefore quite wide. To get it right, + we ensure that any cells which span columns, or which are 'overwritten' + by cells which span columns, are assigned zero width in the cell + sizing. Thus, only the string 'Q1' and the numbers below it are + calculated in estimating the width of column 3, and the phrase + "What time of year?" is not used. However, row-spanned cells are + taken into account. ALL the cells in the leftmost column + have a vertical span (or are occluded by others which do) + but it can still work out a sane width for them. + + """, styNormal)) + + data = self.getDataBlock() + data[0][2] = "Which time of year?" + #data[7][0] = Paragraph("Let's really mess things up with a paragraph",styNormal) + t = Table(data, + #colWidths=(72,72,36,36,36,36,56), + rowHeights=None, + style=sty) + lst.append(t) + + lst.append(Paragraph("""Paragraphs and unsizeable objects in table cells.""", styleSheet['Heading1'])) + + lst.append(Paragraph("""Paragraphs and other flowable objects make table + sizing much harder. In general the height of a paragraph is a function + of its width so you can't ask it how wide it wants to be - and the + REALLY wide all-on-one-line solution is rarely what is wanted. We + refer to Paragraphs and their kin as "unsizeable objects". In this example + we have set the widths of all but the first column. As you can see + it uses all the available space across the page for the first column. + Note also that this fairly large cell does NOT contribute to the + height calculation for its 'row'. Under the hood it is in the + same row as the second Spam, but this row gets a height based on + its own contents and not the cell with the paragraph. + + """, styNormal)) + + + data = self.getDataBlock() + data[5][0] = Paragraph("Let's really mess things up with a paragraph, whose height is a function of the width you give it.",styNormal) + t = Table(data, + colWidths=(None,72,36,36,36,36,56), + rowHeights=None, + style=sty) + lst.append(t) + + + lst.append(Paragraph("""This one demonstrates that our current algorithm + does not cover all cases :-( The height of row 0 is being driven by + the width of the para, which thinks it should fit in 1 column and not 4. + To really get this right would involve multiple passes through all the cells + applying rules until everything which can be sized is sized (possibly + backtracking), applying increasingly dumb and brutal + rules on each pass. + """, styNormal)) + data = self.getDataBlock() + data[0][2] = Paragraph("Let's really mess things up with a paragraph.",styNormal) + data[5][0] = Paragraph("Let's really mess things up with a paragraph, whose height is a function of the width you give it.",styNormal) + t = Table(data, + colWidths=(None,72,36,36,36,36,56), + rowHeights=None, + style=sty) + lst.append(t) + + lst.append(Paragraph("""To avoid these problems remember the golden rule + of ReportLab tables: (1) fix the widths if you can, (2) don't use + a paragraph when a string will do. + """, styNormal)) + + lst.append(Paragraph("""Unsized columns that contain flowables without + precise widths, such as paragraphs and nested tables, + still need to try and keep their content within borders and ideally + even honor percentage requests. This can be tricky--and expensive. + But sometimes you can't follow the golden rules. + """, styNormal)) + + lst.append(Paragraph("""The code first calculates the minimum width + for each unsized column by iterating over every flowable in each column + and remembering the largest minimum width. It then allocates + available space to accomodate the minimum widths. Any remaining space + is divided up, treating a width of '*' as greedy, a width of None as + non-greedy, and a percentage as a weight. If a column is already + wider than its percentage warrants, it is not further expanded, and + the other widths accomodate it. + """, styNormal)) + + lst.append(Paragraph("""For instance, consider this tortured table. + It contains four columns, with widths of None, None, 60%, and 20%, + respectively, and a single row. The first cell contains a paragraph. + The second cell contains a table with fixed column widths that total + about 50% of the total available table width. The third cell contains + a string. The last cell contains a table with no set widths but a + single cell containing a paragraph. + """, styNormal)) + ministy = TableStyle([ + ('GRID', (0,0), (-1,-1), 1.0, colors.black), + ]) + nested1 = [Paragraph( + 'This is a paragraph. The column has a width of None.', + styNormal)] + nested2 = [Table( + [[Paragraph( + 'This table is set to take up two and a half inches. The ' + 'column that holds it has a width of None.', styNormal)]], + colWidths=(180,), + rowHeights=None, + style=ministy)] + nested3 = '60% width' + nested4 = [Table( + [[[Paragraph( + "This is a table with a paragraph in it but no width set. " + "The column width in the containing table is 20%.", + styNormal)]]], + colWidths=(None,), + rowHeights=None, + style=ministy)] + t = Table([[nested1, nested2, nested3, nested4]], + colWidths=(None, None, '60%', '20%'), + rowHeights=None, + style=ministy) + lst.append(t) + + lst.append(Paragraph("""Notice that the second column does expand to + account for the minimum size of its contents; and that the remaining + space goes to the third column, in an attempt to honor the '60%' + request as much as possible. This is reminiscent of the typical HTML + browser approach to tables.""", styNormal)) + + lst.append(Paragraph("""To get an idea of how potentially expensive + this is, consider the case of the last column: the table gets the + minimum width of every flowable of every cell in the column. In this + case one of the flowables is a table with a column without a set + width, so the nested table must itself iterate over its flowables. + The contained paragraph then calculates the width of every word in it + to see what the biggest word is, given the set font face and size. It + is easy to imagine creating a structure of this sort that took an + unacceptably large amount of time to calculate. Remember the golden + rule, if you can. """, styNormal)) + + lst.append(Paragraph("""This code does not yet handle spans well.""", + styNormal)) + + SimpleDocTemplate(outputfile('test_table_layout.pdf'), showBoundary=1).build(lst) + +def makeSuite(): + return makeSuiteForClasses(TableTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + print 'saved '+outputfile('test_table_layout.pdf') + printLocation() diff --git a/bin/reportlab/test/test_tools_pythonpoint.py b/bin/reportlab/test/test_tools_pythonpoint.py new file mode 100644 index 00000000000..bddd0138096 --- /dev/null +++ b/bin/reportlab/test/test_tools_pythonpoint.py @@ -0,0 +1,39 @@ +"""Tests for the PythonPoint tool. +""" +import os, sys, string +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation +import reportlab + +class PythonPointTestCase(unittest.TestCase): + "Some very crude tests on PythonPoint." + def test0(self): + "Test if pythonpoint.pdf can be created from pythonpoint.xml." + + join, dirname, isfile, abspath = os.path.join, os.path.dirname, os.path.isfile, os.path.abspath + rlDir = abspath(dirname(reportlab.__file__)) + from reportlab.tools.pythonpoint import pythonpoint + from reportlab.lib.utils import isCompactDistro, open_for_read + ppDir = dirname(pythonpoint.__file__) + xml = join(ppDir, 'demos', 'pythonpoint.xml') + datafilename = 'pythonpoint.pdf' + outDir = outputfile('') + if isCompactDistro(): + cwd = None + xml = open_for_read(xml) + else: + cwd = os.getcwd() + os.chdir(join(ppDir, 'demos')) + pdf = join(outDir, datafilename) + if isfile(pdf): os.remove(pdf) + pythonpoint.process(xml, outDir=outDir, verbose=0, datafilename=datafilename) + if cwd: os.chdir(cwd) + assert os.path.exists(pdf) + +def makeSuite(): + return makeSuiteForClasses(PythonPointTestCase) + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_utils.py b/bin/reportlab/test/test_utils.py new file mode 100644 index 00000000000..18f8790f175 --- /dev/null +++ b/bin/reportlab/test/test_utils.py @@ -0,0 +1,37 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +__version__='''$Id: test_utils.py 2619 2005-06-24 14:49:15Z rgbecker $''' +__doc__="""Test reportlab.lib.util module""" + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + + +class FmtTestCase(unittest.TestCase): + + def testFmt(self): + from reportlab.lib.utils import FmtSelfDict + class MixedIn(FmtSelfDict): + def __init__(self): + self.a = 'AA' + self._b = '_BB' + self.d = '(overridden)' + obj = MixedIn() + self.assertEqual('blah', obj._fmt('blah')) + self.assertEqual('blah %', obj._fmt('blah %%')) + self.assertRaises(ValueError, obj._fmt, 'blah %') + self.assertEqual( + 'moon AA june_BB spoon %(a)sCC ni', + obj._fmt('moon %(a)s june%(_b)s spoon %%(a)s%(c)s %(d)s', c='CC', C='boon', d='ni')) + self.assertRaises(AttributeError, obj._fmt, '%(c)s') # XXX bit weird, can this be changed? + + +def makeSuite(): + return makeSuiteForClasses(FmtTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_widgetbase_tpc.py b/bin/reportlab/test/test_widgetbase_tpc.py new file mode 100644 index 00000000000..4e8c0b1e33a --- /dev/null +++ b/bin/reportlab/test/test_widgetbase_tpc.py @@ -0,0 +1,138 @@ +#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/test/test_widgetbase_tpc.py +""" +Tests for TypedPropertyCollection class. +""" + +import os, sys, copy +from os.path import join, basename, splitext + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, printLocation + +from reportlab.graphics.widgetbase import PropHolder, TypedPropertyCollection +from reportlab.lib.attrmap import AttrMap, AttrMapValue +from reportlab.lib.validators import isNumber + + +TPC = TypedPropertyCollection + + +class PH(PropHolder): + _attrMap = AttrMap( + a = AttrMapValue(isNumber), + b = AttrMapValue(isNumber) + ) + + +class APH(PH): + def __init__(self): + self.a = 1 + + +class BPH(APH): + def __init__(self): + APH.__init__(self) + + def __getattr__(self,name): + if name=='b': return -1 + raise AttributeError + + +class TPCTestCase(unittest.TestCase): + "Test TypedPropertyCollection class." + + def test0(self): + "Test setting an invalid collective attribute." + + t = TPC(PH) + try: + t.c = 42 + except AttributeError: + pass + + + def test1(self): + "Test setting a valid collective attribute." + + t = TPC(PH) + t.a = 42 + assert t.a == 42 + + + def test2(self): + "Test setting a valid collective attribute with an invalid value." + + t = TPC(PH) + try: + t.a = 'fourty-two' + except AttributeError: + pass + + + def test3(self): + "Test setting a valid collective attribute with a convertible invalid value." + + t = TPC(PH) + t.a = '42' + assert t.a == '42' # Or should it rather be an integer? + + + def test4(self): + "Test accessing an unset collective attribute." + + t = TPC(PH) + try: + t.a + except AttributeError: + pass + + + def test5(self): + "Test overwriting a collective attribute in one slot." + + t = TPC(PH) + t.a = 42 + t[0].a = 4242 + assert t[0].a == 4242 + + + def test6(self): + "Test overwriting a one slot attribute with a collective one." + + t = TPC(PH) + t[0].a = 4242 + t.a = 42 + assert t[0].a == 4242 + + + def test7(self): + "Test to ensure we can handle classes with __getattr__ methods" + + a=TypedPropertyCollection(APH) + b=TypedPropertyCollection(BPH) + + a.a=3 + b.a=4 + try: + a.b + assert 1, "Shouldn't be able to see a.b" + except AttributeError: + pass + a.b=0 + assert a.b==0, "Wrong value for "+str(a.b) + assert b.b==-1, "This should call __getattr__ special" + b.b=0 + assert a[0].b==0 + assert b[0].b==-1, "Class __getattr__ should return -1" + + +def makeSuite(): + return makeSuiteForClasses(TPCTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/test_widgets_grids.py b/bin/reportlab/test/test_widgets_grids.py new file mode 100644 index 00000000000..a1d4a07eb6a --- /dev/null +++ b/bin/reportlab/test/test_widgets_grids.py @@ -0,0 +1,454 @@ + +from reportlab.test import unittest +from reportlab.test.utils import makeSuiteForClasses, outputfile, printLocation + +from reportlab.lib import colors +from reportlab.graphics.shapes import Drawing, Group, Line, Rect +from reportlab.graphics.widgetbase import Widget +from reportlab.graphics.widgets.grids import * +from reportlab.graphics import renderPDF +from reportlab.graphics import renderSVG + + +class GridTestCase(unittest.TestCase): + "Testing diagrams containing grid widgets." + + def _test0(self): + "Create color ranges." + + c0, c1 = colors.Color(0, 0, 0), colors.Color(1, 1, 1) + for c in colorRange(c0, c1, 4): + print c + print + + c0, c1 = colors.CMYKColor(0, 0, 0, 0), colors.CMYKColor(0, 0, 0, 1) + for c in colorRange(c0, c1, 4): + print c + print + + c0, c1 = colors.PCMYKColor(0, 0, 0, 0), colors.PCMYKColor(0, 0, 0, 100) + for c in colorRange(c0, c1, 4): + print c + print + + + def makeDrawing0(self): + "Generate a RLG drawing with some uncommented grid samples." + + D = Drawing(450, 650) + + d = 80 + s = 50 + + for row in range(10): + y = 530 - row*d + if row == 0: + for col in range(4): + x = 20 + col*d + g = Grid() + g.x = x + g.y = y + g.width = s + g.height = s + g.useRects = 0 + g.useLines = 1 + if col == 0: + pass + elif col == 1: + g.delta0 = 10 + elif col == 2: + g.orientation = 'horizontal' + elif col == 3: + g.deltaSteps = [5, 10, 20, 30] + g.demo() + D.add(g) + elif row == 1: + for col in range(4): + x = 20 + col*d + g = Grid() + g.y = y + g.x = x + g.width = s + g.height = s + if col == 0: + pass + elif col == 1: + g.delta0 = 10 + elif col == 2: + g.orientation = 'horizontal' + elif col == 3: + g.deltaSteps = [5, 10, 20, 30] + g.useRects = 1 + g.useLines = 0 + g.demo() + D.add(g) + elif row == 2: + for col in range(3): + x = 20 + col*d + g = Grid() + g.x = x + g.y = y + g.width = s + g.height = s + g.useLines = 1 + g.useRects = 1 + if col == 0: + pass + elif col == 1: + g.delta0 = 10 + elif col == 2: + g.orientation = 'horizontal' + g.demo() + D.add(g) + elif row == 3: + for col in range(3): + x = 20 + col*d + sr = ShadedRect() + sr.x = x + sr.y = y + sr.width = s + sr.height = s + sr.fillColorStart = colors.Color(0, 0, 0) + sr.fillColorEnd = colors.Color(1, 1, 1) + if col == 0: + sr.numShades = 5 + elif col == 1: + sr.numShades = 2 + elif col == 2: + sr.numShades = 1 + sr.demo() + D.add(sr) + elif row == 4: + for col in range(3): + x = 20 + col*d + sr = ShadedRect() + sr.x = x + sr.y = y + sr.width = s + sr.height = s + sr.fillColorStart = colors.red + sr.fillColorEnd = colors.blue + sr.orientation = 'horizontal' + if col == 0: + sr.numShades = 10 + elif col == 1: + sr.numShades = 20 + elif col == 2: + sr.numShades = 50 + sr.demo() + D.add(sr) + elif row == 5: + for col in range(3): + x = 20 + col*d + sr = ShadedRect() + sr.x = x + sr.y = y + sr.width = s + sr.height = s + sr.fillColorStart = colors.white + sr.fillColorEnd = colors.green + sr.orientation = 'horizontal' + if col == 0: + sr.numShades = 10 + elif col == 1: + sr.numShades = 20 + sr.orientation = 'vertical' + elif col == 2: + sr.numShades = 50 + sr.demo() + D.add(sr) + elif row == 6: + for col in range(3): + x = 20 + col*d + sr = ShadedRect() + sr.x = x + sr.y = y+s + sr.width = s + sr.height = -s + sr.fillColorStart = colors.white + sr.fillColorEnd = colors.green + sr.orientation = 'horizontal' + if col == 0: + sr.numShades = 10 + elif col == 1: + sr.numShades = 20 + sr.orientation = 'vertical' + elif col == 2: + sr.numShades = 50 + sr.demo() + D.add(sr) + + return D + + + def makeDrawing1(self): + "Generate a RLG drawing with some uncommented grid samples." + + D = Drawing(450, 650) + + d = 80 + s = 50 + + for row in range(2): + y = 530 - row*d + if row == 0: + for col in range(4): + x = 20 + col*d + g = DoubleGrid() + g.x = x + g.y = y + g.width = s + g.height = s + + # This should be done implicitely... + g.grid0.x = x + g.grid0.y = y + g.grid1.x = x + g.grid1.y = y + g.grid0.width = s + g.grid0.height = s + g.grid1.width = s + g.grid1.height = s + + if col == 0: + pass + elif col == 1: + g.grid0.delta0 = 10 + elif col == 2: + g.grid0.delta0 = 5 + elif col == 3: + g.grid0.deltaSteps = [5, 10, 20, 30] + g.demo() + D.add(g) + elif row == 1: + for col in range(4): + x = 20 + col*d + g = DoubleGrid() + g.x = x + g.y = y + g.width = s + g.height = s + + # This should be done implicitely... + g.grid0.x = x + g.grid0.y = y + g.grid1.x = x + g.grid1.y = y + g.grid0.width = s + g.grid0.height = s + g.grid1.width = s + g.grid1.height = s + + if col == 0: + g.grid0.useRects = 0 + g.grid0.useLines = 1 + g.grid1.useRects = 0 + g.grid1.useLines = 1 + elif col == 1: + g.grid0.useRects = 1 + g.grid0.useLines = 1 + g.grid1.useRects = 0 + g.grid1.useLines = 1 + elif col == 2: + g.grid0.useRects = 1 + g.grid0.useLines = 0 + g.grid1.useRects = 0 + g.grid1.useLines = 1 + elif col == 3: + g.grid0.useRects = 1 + g.grid0.useLines = 0 + g.grid1.useRects = 1 + g.grid1.useLines = 0 + g.demo() + D.add(g) + + return D + + + def makeDrawing2(self): + "Generate a RLG drawing with some uncommented grid samples." + + D = Drawing(450, 650) + + d = 80 + s = 50 + + for row in range(10): + y = 530 - row*d + if row == 0: + for col in range(4): + x = 20 + col*d + g = Grid() + g.x = x + g.y = y + g.width = s + g.height = s + g.useRects = 0 + g.useLines = 1 + if col == 0: + pass + elif col == 1: + g.delta0 = 10 + elif col == 2: + g.orientation = 'horizontal' + elif col == 3: + g.deltaSteps = [5, 10, 20, 30] + g.demo() + D.add(g) + elif row == 1: + for col in range(4): + x = 20 + col*d + g = Grid() + g.y = y + g.x = x + g.width = s + g.height = s + if col == 0: + pass + elif col == 1: + g.delta0 = 10 + elif col == 2: + g.orientation = 'horizontal' + elif col == 3: + g.deltaSteps = [5, 10, 20, 30] + g.useRects = 1 + g.useLines = 0 + g.demo() + D.add(g) + elif row == 2: + for col in range(3): + x = 20 + col*d + g = Grid() + g.x = x + g.y = y + g.width = s + g.height = s + g.useLines = 1 + g.useRects = 1 + if col == 0: + pass + elif col == 1: + g.delta0 = 10 + elif col == 2: + g.orientation = 'horizontal' + g.demo() + D.add(g) + elif row == 3: + for col in range(3): + x = 20 + col*d + sr = ShadedRect() + sr.x = x + sr.y = y + sr.width = s + sr.height = s + ## sr.fillColorStart = colors.Color(0, 0, 0) + ## sr.fillColorEnd = colors.Color(1, 1, 1) + sr.fillColorStart = colors.CMYKColor(0, 0, 0, 0) + sr.fillColorEnd = colors.CMYKColor(1, 1, 1, 1) + if col == 0: + sr.numShades = 5 + elif col == 1: + sr.numShades = 2 + elif col == 2: + sr.numShades = 1 + sr.demo() + D.add(sr) + elif row == 4: + for col in range(3): + x = 20 + col*d + sr = ShadedRect() + sr.x = x + sr.y = y + sr.width = s + sr.height = s + ## sr.fillColorStart = colors.red + ## sr.fillColorEnd = colors.blue + sr.fillColorStart = colors.CMYKColor(1, 0, 0, 0) + sr.fillColorEnd = colors.CMYKColor(0, 0, 1, 0) + sr.orientation = 'horizontal' + if col == 0: + sr.numShades = 10 + elif col == 1: + sr.numShades = 20 + elif col == 2: + sr.numShades = 50 + sr.demo() + D.add(sr) + elif row == 5: + for col in range(3): + x = 20 + col*d + sr = ShadedRect() + sr.x = x + sr.y = y + sr.width = s + sr.height = s + ## sr.fillColorStart = colors.white + ## sr.fillColorEnd = colors.green + sr.fillColorStart = colors.PCMYKColor(11.0,11.0,72.0,0.0, spotName='PANTONE 458 CV',density=1.00) + sr.fillColorEnd = colors.PCMYKColor(100.0,65.0,0.0,30.0, spotName='PANTONE 288 CV',density=1.00) + sr.orientation = 'horizontal' + if col == 0: + sr.numShades = 10 + elif col == 1: + sr.numShades = 20 + sr.orientation = 'vertical' + elif col == 2: + sr.numShades = 50 + sr.demo() + D.add(sr) + elif row == 6: + for col in range(3): + x = 20 + col*d + sr = ShadedRect() + sr.x = x + sr.y = y+s + sr.width = s + sr.height = -s + sr.fillColorStart = colors.white + sr.fillColorEnd = colors.green + sr.orientation = 'horizontal' + if col == 0: + sr.numShades = 10 + elif col == 1: + sr.numShades = 20 + sr.orientation = 'vertical' + elif col == 2: + sr.numShades = 50 + sr.demo() + D.add(sr) + + return D + + + def test0(self): + "Generate PDF and SVG documents of first sample drawing." + + d = self.makeDrawing0() + renderPDF.drawToFile(d, outputfile('test_widgets_grids0.pdf')) + renderSVG.drawToFile(d, outputfile('test_widgets_grids0.svg')) + + + def test1(self): + "Generate PDF and SVG documents of second sample drawing." + + d = self.makeDrawing1() + renderPDF.drawToFile(d, outputfile('test_widgets_grids1.pdf')) + renderSVG.drawToFile(d, outputfile('test_widgets_grids1.svg')) + + + def test2(self): + "Generate PDF and SVG documents of third sample drawing." + + d = self.makeDrawing2() + renderPDF.drawToFile(d, outputfile('test_widgets_grids2.pdf')) + renderSVG.drawToFile(d, outputfile('test_widgets_grids2.svg')) + + +def makeSuite(): + return makeSuiteForClasses(GridTestCase) + + +#noruntests +if __name__ == "__main__": + unittest.TextTestRunner().run(makeSuite()) + printLocation() diff --git a/bin/reportlab/test/unittest.py b/bin/reportlab/test/unittest.py new file mode 100644 index 00000000000..2523f431c40 --- /dev/null +++ b/bin/reportlab/test/unittest.py @@ -0,0 +1,723 @@ +#!/usr/bin/env python +''' +Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's +Smalltalk testing framework. + +This module contains the core framework classes that form the basis of +specific test cases and suites (TestCase, TestSuite etc.), and also a +text-based utility class for running the tests and reporting the results + (TextTestRunner). + +Simple usage: + + import unittest + + class IntegerArithmenticTestCase(unittest.TestCase): + def testAdd(self): ## test method names begin 'test*' + self.assertEquals((1 + 2), 3) + self.assertEquals(0 + 1, 1) + def testMultiply(self): + self.assertEquals((0 * 10), 0) + self.assertEquals((5 * 8), 40) + + if __name__ == '__main__': + unittest.main() + +Further information is available in the bundled documentation, and from + + http://pyunit.sourceforge.net/ + +Copyright (c) 1999, 2000, 2001 Steve Purcell +This module is free software, and you may redistribute it and/or modify +it under the same terms as Python itself, so long as this copyright message +and disclaimer are retained in their original form. + +IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +''' + +__author__ = "Steve Purcell" +__email__ = "stephen_purcell at yahoo dot com" +__version__ = "#Revision: 1.43 $"[11:-2] + +import time +import sys +import traceback +import string +import os +import types + +############################################################################## +# Test framework core +############################################################################## + +class TestResult: + """Holder for test result information. + + Test results are automatically managed by the TestCase and TestSuite + classes, and do not need to be explicitly manipulated by writers of tests. + + Each instance holds the total number of tests run, and collections of + failures and errors that occurred among those test runs. The collections + contain tuples of (testcase, exceptioninfo), where exceptioninfo is the + formatted traceback of the error that occurred. + """ + def __init__(self): + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.shouldStop = 0 + + def startTest(self, test): + "Called when the given test is about to be run" + self.testsRun = self.testsRun + 1 + + def stopTest(self, test): + "Called when the given test has been run" + pass + + def addError(self, test, err): + """Called when an error has occurred. 'err' is a tuple of values as + returned by sys.exc_info(). + """ + self.errors.append((test, self._exc_info_to_string(err))) + + def addFailure(self, test, err): + """Called when an error has occurred. 'err' is a tuple of values as + returned by sys.exc_info().""" + self.failures.append((test, self._exc_info_to_string(err))) + + def addSuccess(self, test): + "Called when a test has completed successfully" + pass + + def wasSuccessful(self): + "Tells whether or not this result was a success" + return len(self.failures) == len(self.errors) == 0 + + def stop(self): + "Indicates that the tests should be aborted" + self.shouldStop = 1 + + def _exc_info_to_string(self, err): + """Converts a sys.exc_info()-style tuple of values into a string.""" + return string.join(apply(traceback.format_exception, err), '') + + def __repr__(self): + return "<%s run=%i errors=%i failures=%i>" % \ + (self.__class__, self.testsRun, len(self.errors), + len(self.failures)) + + +class TestCase: + """A class whose instances are single test cases. + + By default, the test code itself should be placed in a method named + 'runTest'. + + If the fixture may be used for many test cases, create as + many test methods as are needed. When instantiating such a TestCase + subclass, specify in the constructor arguments the name of the test method + that the instance is to execute. + + Test authors should subclass TestCase for their own tests. Construction + and deconstruction of the test's environment ('fixture') can be + implemented by overriding the 'setUp' and 'tearDown' methods respectively. + + If it is necessary to override the __init__ method, the base class + __init__ method must always be called. It is important that subclasses + should not change the signature of their __init__ method, since instances + of the classes are instantiated automatically by parts of the framework + in order to be run. + """ + + # This attribute determines which exception will be raised when + # the instance's assertion methods fail; test methods raising this + # exception will be deemed to have 'failed' rather than 'errored' + + failureException = AssertionError + + def __init__(self, methodName='runTest'): + """Create an instance of the class that will use the named test + method when executed. Raises a ValueError if the instance does + not have a method with the specified name. + """ + try: + self.__testMethodName = methodName + testMethod = getattr(self, methodName) + self.__testMethodDoc = testMethod.__doc__ + except AttributeError: + raise ValueError, "no such test method in %s: %s" % \ + (self.__class__, methodName) + + def setUp(self): + "Hook method for setting up the test fixture before exercising it." + pass + + def tearDown(self): + "Hook method for deconstructing the test fixture after testing it." + pass + + def countTestCases(self): + return 1 + + def defaultTestResult(self): + return TestResult() + + def shortDescription(self): + """Returns a one-line description of the test, or None if no + description has been provided. + + The default implementation of this method returns the first line of + the specified test method's docstring. + """ + doc = self.__testMethodDoc + return doc and string.strip(string.split(doc, "\n")[0]) or None + + def id(self): + return "%s.%s" % (self.__class__, self.__testMethodName) + + def __str__(self): + return "%s (%s)" % (self.__testMethodName, self.__class__) + + def __repr__(self): + return "<%s testMethod=%s>" % \ + (self.__class__, self.__testMethodName) + + def run(self, result=None): + return self(result) + + def __call__(self, result=None): + if result is None: result = self.defaultTestResult() + result.startTest(self) + testMethod = getattr(self, self.__testMethodName) + try: + try: + self.setUp() + except KeyboardInterrupt: + raise + except: + result.addError(self, self.__exc_info()) + return + + ok = 0 + try: + testMethod() + ok = 1 + except self.failureException, e: + result.addFailure(self, self.__exc_info()) + except KeyboardInterrupt: + raise + except: + result.addError(self, self.__exc_info()) + + try: + self.tearDown() + except KeyboardInterrupt: + raise + except: + result.addError(self, self.__exc_info()) + ok = 0 + if ok: result.addSuccess(self) + finally: + result.stopTest(self) + + def debug(self): + """Run the test without collecting errors in a TestResult""" + self.setUp() + getattr(self, self.__testMethodName)() + self.tearDown() + + def __exc_info(self): + """Return a version of sys.exc_info() with the traceback frame + minimised; usually the top level of the traceback frame is not + needed. + """ + exctype, excvalue, tb = sys.exc_info() + if sys.platform[:4] == 'java': ## tracebacks look different in Jython + return (exctype, excvalue, tb) + newtb = tb.tb_next + if newtb is None: + return (exctype, excvalue, tb) + return (exctype, excvalue, newtb) + + def fail(self, msg=None): + """Fail immediately, with the given message.""" + raise self.failureException, msg + + def failIf(self, expr, msg=None): + "Fail the test if the expression is true." + if expr: raise self.failureException, msg + + def failUnless(self, expr, msg=None): + """Fail the test unless the expression is true.""" + if not expr: raise self.failureException, msg + + def failUnlessRaises(self, excClass, callableObj, *args, **kwargs): + """Fail unless an exception of class excClass is thrown + by callableObj when invoked with arguments args and keyword + arguments kwargs. If a different type of exception is + thrown, it will not be caught, and the test case will be + deemed to have suffered an error, exactly as for an + unexpected exception. + """ + try: + apply(callableObj, args, kwargs) + except excClass: + return + else: + if hasattr(excClass,'__name__'): excName = excClass.__name__ + else: excName = str(excClass) + raise self.failureException, excName + + def failUnlessEqual(self, first, second, msg=None): + """Fail if the two objects are unequal as determined by the '!=' + operator. + """ + if first != second: + raise self.failureException, \ + (msg or '%s != %s' % (`first`, `second`)) + + def failIfEqual(self, first, second, msg=None): + """Fail if the two objects are equal as determined by the '==' + operator. + """ + if first == second: + raise self.failureException, \ + (msg or '%s == %s' % (`first`, `second`)) + + assertEqual = assertEquals = failUnlessEqual + + assertNotEqual = assertNotEquals = failIfEqual + + assertRaises = failUnlessRaises + + assert_ = failUnless + + + +class TestSuite: + """A test suite is a composite test consisting of a number of TestCases. + + For use, create an instance of TestSuite, then add test case instances. + When all tests have been added, the suite can be passed to a test + runner, such as TextTestRunner. It will run the individual test cases + in the order in which they were added, aggregating the results. When + subclassing, do not forget to call the base class constructor. + """ + def __init__(self, tests=()): + self._tests = [] + self.addTests(tests) + + def __repr__(self): + return "<%s tests=%s>" % (self.__class__, self._tests) + + __str__ = __repr__ + + def countTestCases(self): + cases = 0 + for test in self._tests: + cases = cases + test.countTestCases() + return cases + + def addTest(self, test): + self._tests.append(test) + + def addTests(self, tests): + for test in tests: + self.addTest(test) + + def run(self, result): + return self(result) + + def __call__(self, result): + for test in self._tests: + if result.shouldStop: + break + test(result) + return result + + def debug(self): + """Run the tests without collecting errors in a TestResult""" + for test in self._tests: test.debug() + + +class FunctionTestCase(TestCase): + """A test case that wraps a test function. + + This is useful for slipping pre-existing test functions into the + PyUnit framework. Optionally, set-up and tidy-up functions can be + supplied. As with TestCase, the tidy-up ('tearDown') function will + always be called if the set-up ('setUp') function ran successfully. + """ + + def __init__(self, testFunc, setUp=None, tearDown=None, + description=None): + TestCase.__init__(self) + self.__setUpFunc = setUp + self.__tearDownFunc = tearDown + self.__testFunc = testFunc + self.__description = description + + def setUp(self): + if self.__setUpFunc is not None: + self.__setUpFunc() + + def tearDown(self): + if self.__tearDownFunc is not None: + self.__tearDownFunc() + + def runTest(self): + self.__testFunc() + + def id(self): + return self.__testFunc.__name__ + + def __str__(self): + return "%s (%s)" % (self.__class__, self.__testFunc.__name__) + + def __repr__(self): + return "<%s testFunc=%s>" % (self.__class__, self.__testFunc) + + def shortDescription(self): + if self.__description is not None: return self.__description + doc = self.__testFunc.__doc__ + return doc and string.strip(string.split(doc, "\n")[0]) or None + + + +############################################################################## +# Locating and loading tests +############################################################################## + +class TestLoader: + """This class is responsible for loading tests according to various + criteria and returning them wrapped in a Test + """ + testMethodPrefix = 'test' + sortTestMethodsUsing = cmp + suiteClass = TestSuite + + def loadTestsFromTestCase(self, testCaseClass): + """Return a suite of all tests cases contained in testCaseClass""" + return self.suiteClass(map(testCaseClass, + self.getTestCaseNames(testCaseClass))) + + def loadTestsFromModule(self, module): + """Return a suite of all tests cases contained in the given module""" + tests = [] + for name in dir(module): + obj = getattr(module, name) + if type(obj) == types.ClassType and issubclass(obj, TestCase): + tests.append(self.loadTestsFromTestCase(obj)) + return self.suiteClass(tests) + + def loadTestsFromName(self, name, module=None): + """Return a suite of all tests cases given a string specifier. + + The name may resolve either to a module, a test case class, a + test method within a test case class, or a callable object which + returns a TestCase or TestSuite instance. + + The method optionally resolves the names relative to a given module. + """ + parts = string.split(name, '.') + if module is None: + if not parts: + raise ValueError, "incomplete test name: %s" % name + else: + parts_copy = parts[:] + while parts_copy: + try: + module = __import__(string.join(parts_copy,'.')) + break + except ImportError: + del parts_copy[-1] + if not parts_copy: raise + parts = parts[1:] + obj = module + for part in parts: + obj = getattr(obj, part) + + import unittest + if type(obj) == types.ModuleType: + return self.loadTestsFromModule(obj) + elif type(obj) == types.ClassType and issubclass(obj, unittest.TestCase): + return self.loadTestsFromTestCase(obj) + elif type(obj) == types.UnboundMethodType: + return obj.im_class(obj.__name__) + elif callable(obj): + test = obj() + if not isinstance(test, unittest.TestCase) and \ + not isinstance(test, unittest.TestSuite): + raise ValueError, \ + "calling %s returned %s, not a test" % (obj,test) + return test + else: + raise ValueError, "don't know how to make test from: %s" % obj + + def loadTestsFromNames(self, names, module=None): + """Return a suite of all tests cases found using the given sequence + of string specifiers. See 'loadTestsFromName()'. + """ + suites = [] + for name in names: + suites.append(self.loadTestsFromName(name, module)) + return self.suiteClass(suites) + + def getTestCaseNames(self, testCaseClass): + """Return a sorted sequence of method names found within testCaseClass + """ + testFnNames = filter(lambda n,p=self.testMethodPrefix: n[:len(p)] == p, + dir(testCaseClass)) + for baseclass in testCaseClass.__bases__: + for testFnName in self.getTestCaseNames(baseclass): + if testFnName not in testFnNames: # handle overridden methods + testFnNames.append(testFnName) + if self.sortTestMethodsUsing: + testFnNames.sort(self.sortTestMethodsUsing) + return testFnNames + + + +defaultTestLoader = TestLoader() + + +############################################################################## +# Patches for old functions: these functions should be considered obsolete +############################################################################## + +def _makeLoader(prefix, sortUsing, suiteClass=None): + loader = TestLoader() + loader.sortTestMethodsUsing = sortUsing + loader.testMethodPrefix = prefix + if suiteClass: loader.suiteClass = suiteClass + return loader + +def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp): + return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass) + +def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite): + return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass) + +def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite): + return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module) + + +############################################################################## +# Text UI +############################################################################## + +class _WritelnDecorator: + """Used to decorate file-like objects with a handy 'writeln' method""" + def __init__(self,stream): + self.stream = stream + + def __getattr__(self, attr): + return getattr(self.stream,attr) + + def writeln(self, *args): + if args: apply(self.write, args) + self.write('\n') # text-mode streams translate to \r\n if needed + + +class _TextTestResult(TestResult): + """A test result class that can print formatted text results to a stream. + + Used by TextTestRunner. + """ + separator1 = '=' * 70 + separator2 = '-' * 70 + + def __init__(self, stream, descriptions, verbosity): + TestResult.__init__(self) + self.stream = stream + self.showAll = verbosity > 1 + self.dots = verbosity == 1 + self.descriptions = descriptions + + def getDescription(self, test): + if self.descriptions: + return test.shortDescription() or str(test) + else: + return str(test) + + def startTest(self, test): + TestResult.startTest(self, test) + if self.showAll: + self.stream.write(self.getDescription(test)) + self.stream.write(" ... ") + + def addSuccess(self, test): + TestResult.addSuccess(self, test) + if self.showAll: + self.stream.writeln("ok") + elif self.dots: + self.stream.write('.') + + def addError(self, test, err): + TestResult.addError(self, test, err) + if self.showAll: + self.stream.writeln("ERROR") + elif self.dots: + self.stream.write('E') + + def addFailure(self, test, err): + TestResult.addFailure(self, test, err) + if self.showAll: + self.stream.writeln("FAIL") + elif self.dots: + self.stream.write('F') + + def printErrors(self): + if self.dots or self.showAll: + self.stream.writeln() + self.printErrorList('ERROR', self.errors) + self.printErrorList('FAIL', self.failures) + + def printErrorList(self, flavour, errors): + for test, err in errors: + self.stream.writeln(self.separator1) + self.stream.writeln("%s: %s" % (flavour,self.getDescription(test))) + self.stream.writeln(self.separator2) + self.stream.writeln("%s" % err) + + +class TextTestRunner: + """A test runner class that displays results in textual form. + + It prints out the names of tests as they are run, errors as they + occur, and a summary of the results at the end of the test run. + """ + def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1): + self.stream = _WritelnDecorator(stream) + self.descriptions = descriptions + self.verbosity = verbosity + + def _makeResult(self): + return _TextTestResult(self.stream, self.descriptions, self.verbosity) + + def run(self, test): + "Run the given test case or test suite." + result = self._makeResult() + startTime = time.time() + test(result) + stopTime = time.time() + timeTaken = float(stopTime - startTime) + result.printErrors() + self.stream.writeln(result.separator2) + run = result.testsRun + self.stream.writeln("Ran %d test%s in %.3fs" % + (run, run != 1 and "s" or "", timeTaken)) + self.stream.writeln() + if not result.wasSuccessful(): + self.stream.write("FAILED (") + failed, errored = map(len, (result.failures, result.errors)) + if failed: + self.stream.write("failures=%d" % failed) + if errored: + if failed: self.stream.write(", ") + self.stream.write("errors=%d" % errored) + self.stream.writeln(")") + else: + self.stream.writeln("OK") + return result + + + +############################################################################## +# Facilities for running tests from the command line +############################################################################## + +class TestProgram: + """A command-line program that runs a set of tests; this is primarily + for making test modules conveniently executable. + """ + USAGE = """\ +Usage: %(progName)s [options] [test] [...] + +Options: + -h, --help Show this message + -v, --verbose Verbose output + -q, --quiet Minimal output + +Examples: + %(progName)s - run default set of tests + %(progName)s MyTestSuite - run suite 'MyTestSuite' + %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething + %(progName)s MyTestCase - run all 'test*' test methods + in MyTestCase +""" + def __init__(self, module='__main__', defaultTest=None, + argv=None, testRunner=None, testLoader=defaultTestLoader): + if type(module) == type(''): + self.module = __import__(module) + for part in string.split(module,'.')[1:]: + self.module = getattr(self.module, part) + else: + self.module = module + if argv is None: + argv = sys.argv + self.verbosity = 1 + self.defaultTest = defaultTest + self.testRunner = testRunner + self.testLoader = testLoader + self.progName = os.path.basename(argv[0]) + self.parseArgs(argv) + self.runTests() + + def usageExit(self, msg=None): + if msg: print msg + print self.USAGE % self.__dict__ + sys.exit(2) + + def parseArgs(self, argv): + import getopt + try: + options, args = getopt.getopt(argv[1:], 'hHvq', + ['help','verbose','quiet']) + for opt, value in options: + if opt in ('-h','-H','--help'): + self.usageExit() + if opt in ('-q','--quiet'): + self.verbosity = 0 + if opt in ('-v','--verbose'): + self.verbosity = 2 + if len(args) == 0 and self.defaultTest is None: + self.test = self.testLoader.loadTestsFromModule(self.module) + return + if len(args) > 0: + self.testNames = args + else: + self.testNames = (self.defaultTest,) + self.createTests() + except getopt.error, msg: + self.usageExit(msg) + + def createTests(self): + self.test = self.testLoader.loadTestsFromNames(self.testNames, + self.module) + + def runTests(self): + if self.testRunner is None: + self.testRunner = TextTestRunner(verbosity=self.verbosity) + result = self.testRunner.run(self.test) + sys.exit(not result.wasSuccessful()) + +main = TestProgram + + +############################################################################## +# Executing this module from the command line +############################################################################## + +if __name__ == "__main__": + main(module=None) diff --git a/bin/reportlab/test/utils.py b/bin/reportlab/test/utils.py new file mode 100644 index 00000000000..7eb2f3c6c35 --- /dev/null +++ b/bin/reportlab/test/utils.py @@ -0,0 +1,308 @@ +"""Utilities for testing Python packages. +""" +import sys, os, string, fnmatch, copy, re +from ConfigParser import ConfigParser +from reportlab.test import unittest + +# Helper functions. +def isWritable(D): + try: + fn = '00DELETE.ME' + f = open(fn, 'w') + f.write('test of writability - can be deleted') + f.close() + if os.path.isfile(fn): + os.remove(fn) + return 1 + except: + return 0 + +_TEST_DIR_IS_WRITABLE = None #not known yet +_OUTDIR = None +def canWriteTestOutputHere(): + """Is it a writable file system distro being invoked within + test directory? If so, can write test output here. If not, + it had better go in a temp directory. Only do this once per + process""" + + global _TEST_DIR_IS_WRITABLE, _OUTDIR + if _TEST_DIR_IS_WRITABLE is not None: + return _TEST_DIR_IS_WRITABLE + + D = [d[9:] for d in sys.argv if d.startswith('--outdir=')] + if D: + _OUTDIR = D[-1] + try: + os.makedirs(_OUTDIR) + except: + pass + map(sys.argv.remove,D) + _TEST_DIR_IS_WRITABLE = isWritable(_OUTDIR) + else: + from reportlab.lib.utils import isSourceDistro + if isSourceDistro(): + curDir = os.getcwd() + parentDir = os.path.dirname(curDir) + if curDir.endswith('test') and parentDir.endswith('reportlab'): + #we're probably being run within the test directory. + #now check it's writeable. + _TEST_DIR_IS_WRITABLE = isWritable(curDir) + _OUTDIR = curDir + return _TEST_DIR_IS_WRITABLE + +def outputfile(fn): + """This works out where to write test output. If running + code in a zip file or a locked down file system, this will be a + temp directory; otherwise, the output of 'test_foo.py' will + normally be a file called 'test_foo.pdf', next door. + """ + if canWriteTestOutputHere(): + D = _OUTDIR + else: + from reportlab.lib.utils import isSourceDistro, get_rl_tempdir + D = get_rl_tempdir('reportlab_test') + if fn: D = os.path.join(D,fn) + return D + +def printLocation(depth=1): + if sys._getframe(depth).f_locals.get('__name__')=='__main__': + outDir = outputfile('') + if outDir!=_OUTDIR: + print 'Logs and output files written to folder "%s"' % outDir + +def makeSuiteForClasses(*classes): + "Return a test suite with tests loaded from provided classes." + + suite = unittest.TestSuite() + loader = unittest.TestLoader() + for C in classes: + suite.addTest(loader.loadTestsFromTestCase(C)) + return suite + +def getCVSEntries(folder, files=1, folders=0): + """Returns a list of filenames as listed in the CVS/Entries file. + + 'folder' is the folder that should contain the CVS subfolder. + If there is no such subfolder an empty list is returned. + 'files' is a boolean; 1 and 0 means to return files or not. + 'folders' is a boolean; 1 and 0 means to return folders or not. + """ + + join = os.path.join + split = string.split + + # If CVS subfolder doesn't exist return empty list. + try: + f = open(join(folder, 'CVS', 'Entries')) + except IOError: + return [] + + # Return names of files and/or folders in CVS/Entries files. + allEntries = [] + for line in f.readlines(): + if folders and line[0] == 'D' \ + or files and line[0] != 'D': + entry = split(line, '/')[1] + if entry: + allEntries.append(join(folder, entry)) + + return allEntries + + +# Still experimental class extending ConfigParser's behaviour. +class ExtConfigParser(ConfigParser): + "A slightly extended version to return lists of strings." + + pat = re.compile('\s*\[.*\]\s*') + + def getstringlist(self, section, option): + "Coerce option to a list of strings or return unchanged if that fails." + + value = apply(ConfigParser.get, (self, section, option)) + + # This seems to allow for newlines inside values + # of the config file, but be careful!! + val = string.replace(value, '\n', '') + + if self.pat.match(val): + return eval(val) + else: + return value + + +# This class as suggested by /F with an additional hook +# to be able to filter filenames. + +class GlobDirectoryWalker: + "A forward iterator that traverses files in a directory tree." + + def __init__(self, directory, pattern='*'): + self.index = 0 + self.pattern = pattern + directory.replace('/',os.sep) + if os.path.isdir(directory): + self.stack = [directory] + self.files = [] + else: + from reportlab.lib.utils import isCompactDistro, __loader__, rl_isdir + if not isCompactDistro() or not __loader__ or not rl_isdir(directory): + raise ValueError('"%s" is not a directory' % directory) + self.directory = directory[len(__loader__.archive)+len(os.sep):] + pfx = self.directory+os.sep + n = len(pfx) + self.files = map(lambda x, n=n: x[n:],filter(lambda x,pfx=pfx: x.startswith(pfx),__loader__._files.keys())) + self.stack = [] + + def __getitem__(self, index): + while 1: + try: + file = self.files[self.index] + self.index = self.index + 1 + except IndexError: + # pop next directory from stack + self.directory = self.stack.pop() + self.files = os.listdir(self.directory) + # now call the hook + self.files = self.filterFiles(self.directory, self.files) + self.index = 0 + else: + # got a filename + fullname = os.path.join(self.directory, file) + if os.path.isdir(fullname) and not os.path.islink(fullname): + self.stack.append(fullname) + if fnmatch.fnmatch(file, self.pattern): + return fullname + + def filterFiles(self, folder, files): + "Filter hook, overwrite in subclasses as needed." + + return files + + +class RestrictedGlobDirectoryWalker(GlobDirectoryWalker): + "An restricted directory tree iterator." + + def __init__(self, directory, pattern='*', ignore=None): + apply(GlobDirectoryWalker.__init__, (self, directory, pattern)) + + if ignore == None: + ignore = [] + self.ignoredPatterns = [] + if type(ignore) == type([]): + for p in ignore: + self.ignoredPatterns.append(p) + elif type(ignore) == type(''): + self.ignoredPatterns.append(ignore) + + + def filterFiles(self, folder, files): + "Filters all items from files matching patterns to ignore." + + indicesToDelete = [] + for i in xrange(len(files)): + f = files[i] + for p in self.ignoredPatterns: + if fnmatch.fnmatch(f, p): + indicesToDelete.append(i) + indicesToDelete.reverse() + for i in indicesToDelete: + del files[i] + + return files + + +class CVSGlobDirectoryWalker(GlobDirectoryWalker): + "An directory tree iterator that checks for CVS data." + + def filterFiles(self, folder, files): + """Filters files not listed in CVS subfolder. + + This will look in the CVS subfolder of 'folder' for + a file named 'Entries' and filter all elements from + the 'files' list that are not listed in 'Entries'. + """ + + join = os.path.join + cvsFiles = getCVSEntries(folder) + if cvsFiles: + indicesToDelete = [] + for i in xrange(len(files)): + f = files[i] + if join(folder, f) not in cvsFiles: + indicesToDelete.append(i) + indicesToDelete.reverse() + for i in indicesToDelete: + del files[i] + + return files + + +# An experimental untested base class with additional 'security'. + +class SecureTestCase(unittest.TestCase): + """Secure testing base class with additional pre- and postconditions. + + We try to ensure that each test leaves the environment it has + found unchanged after the test is performed, successful or not. + + Currently we restore sys.path and the working directory, but more + of this could be added easily, like removing temporary files or + similar things. + + Use this as a base class replacing unittest.TestCase and call + these methods in subclassed versions before doing your own + business! + """ + + def setUp(self): + "Remember sys.path and current working directory." + + self._initialPath = copy.copy(sys.path) + self._initialWorkDir = os.getcwd() + + + def tearDown(self): + "Restore previous sys.path and working directory." + + sys.path = self._initialPath + os.chdir(self._initialWorkDir) + + +class ScriptThatMakesFileTest(unittest.TestCase): + """Runs a Python script at OS level, expecting it to produce a file. + + It CDs to the working directory to run the script.""" + def __init__(self, scriptDir, scriptName, outFileName, verbose=0): + self.scriptDir = scriptDir + self.scriptName = scriptName + self.outFileName = outFileName + self.verbose = verbose + # normally, each instance is told which method to run) + unittest.TestCase.__init__(self) + + def setUp(self): + + self.cwd = os.getcwd() + #change to reportlab directory first, so that + #relative paths may be given to scriptdir + import reportlab + self.rl_dir = os.path.dirname(reportlab.__file__) + self.fn = __file__ + os.chdir(self.rl_dir) + + os.chdir(self.scriptDir) + assert os.path.isfile(self.scriptName), "Script %s not found!" % self.scriptName + if os.path.isfile(self.outFileName): + os.remove(self.outFileName) + + def tearDown(self): + os.chdir(self.cwd) + + def runTest(self): + fmt = sys.platform=='win32' and '"%s" %s' or '%s %s' + p = os.popen(fmt % (sys.executable,self.scriptName),'r') + out = p.read() + if self.verbose: + print out + status = p.close() + assert os.path.isfile(self.outFileName), "File %s not created!" % self.outFileName