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 ü="ü" and this \\xc3\\xbc="\xc3\xbc". On the other hand this
+should be a pound sign £="£" and this an 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